{"id":"2b5d5dc6-1ee1-4f2c-87e2-e46d958995d6","entityType":"agent","slug":"clawhub-chrischall-maxpreps-mcp","name":"maxpreps-mcp","canonicalUrl":"https://www.xpersona.co/agent/clawhub-chrischall-maxpreps-mcp","canonicalPath":"/agent/clawhub-chrischall-maxpreps-mcp","generatedAt":"2026-10-11T14:14:51.411Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T11:29:15.681Z","emptyReason":null},"description":"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school. Skill: maxpreps-mcp Owner: chrischall Summary: Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school. Tags: latest:1.1.8 Version history: v1.1.8 | 2026-10-09T23:25:57.242Z | auto maxpreps-mcp v1.1.8 - Removed skill-card.md, consolidating documentation into SKILL.md. - No changes to functionality;","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:maxpreps-mcp","sourceUrl":"https://clawhub.ai/chrischall/maxpreps-mcp","homepage":"https://clawhub.ai/chrischall/skills/maxpreps-mcp","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/chrischall/maxpreps-mcp","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/chrischall/skills/maxpreps-mcp","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":61,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for an"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T11:29:15.681Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T11:29:15.681Z","emptyReason":null},"stars":null,"forks":null,"downloads":1076,"packageName":null,"latestVersion":"1.1.8","tractionLabel":"1.1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T11:29:15.614Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T11:29:15.681Z","lastCrawledAt":"2026-10-11T11:29:15.614Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T11:29:15.614Z","lastVerifiedAt":null,"highlights":[{"version":"1.1.8","createdAt":"2026-10-09T23:25:57.242Z","changelog":"maxpreps-mcp v1.1.8 - Removed skill-card.md, consolidating documentation into SKILL.md. - No changes to functionality; CLI usage and data output remain unchanged. - Clean-up and simplification of files provided with the skill.","fileCount":5,"zipByteSize":12752},{"version":"1.1.7","createdAt":"2026-10-07T13:36:55.874Z","changelog":"- Removed the skill card file (skill-card.md). - No user-facing changes to functionality or API.","fileCount":5,"zipByteSize":12742},{"version":"1.1.6","createdAt":"2026-10-05T02:48:40.687Z","changelog":"- Removed the sample file skill-card.md. - No user-facing changes to functionality or documentation.","fileCount":5,"zipByteSize":12658},{"version":"1.1.5","createdAt":"2026-10-03T01:45:33.120Z","changelog":"- Removed the file skill-card.md. - No changes to functionality or documentation. - Maintenance update only.","fileCount":5,"zipByteSize":12797},{"version":"1.1.4","createdAt":"2026-09-30T16:59:34.224Z","changelog":"- Removed the file skill-card.md to streamline the skill package. - No changes to core functionality or behavior.","fileCount":5,"zipByteSize":12665},{"version":"1.1.3","createdAt":"2026-09-25T15:51:37.184Z","changelog":"- Removed the file skill-card.md. - No user-facing features or interface changes in this version. - Documentation and functionality remain unchanged.","fileCount":5,"zipByteSize":12653},{"version":"1.1.2","createdAt":"2026-09-23T21:40:03.123Z","changelog":"- Improved `homeAwayType` decoding: now recognizes `2` as \"neutral site\" and marks unknown values as `unknown`. - Documentation updates in SKILL.md to clarify `homeAwayType` and related edge cases. - Removed obsolete skill-card.md file for cleanup. - Reference docs (recipes.md) updated.","fileCount":5,"zipByteSize":12782},{"version":"1.1.1","createdAt":"2026-09-23T15:45:05.826Z","changelog":"- Removed the sample file skill-card.md. - No changes to code or documentation besides file cleanup.","fileCount":5,"zipByteSize":12751}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17cjx1a349nz5apaqp02vgz4h85728z:maxpreps-mcp","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T14:14:51.408Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-chrischall-maxpreps-mcp/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T11:29:15.681Z","emptyReason":null},"readme":"Skill: maxpreps-mcp\n\nOwner: chrischall\n\nSummary: Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\n\nTags: latest:1.1.8\n\nVersion history:\n\nv1.1.8 | 2026-10-09T23:25:57.242Z | auto\n\nmaxpreps-mcp v1.1.8\n\n- Removed skill-card.md, consolidating documentation into SKILL.md.\n- No changes to functionality; CLI usage and data output remain unchanged.\n- Clean-up and simplification of files provided with the skill.\n\nv1.1.7 | 2026-10-07T13:36:55.874Z | auto\n\n- Removed the skill card file (skill-card.md).\n- No user-facing changes to functionality or API.\n\nv1.1.6 | 2026-10-05T02:48:40.687Z | auto\n\n- Removed the sample file skill-card.md.\n- No user-facing changes to functionality or documentation.\n\nv1.1.5 | 2026-10-03T01:45:33.120Z | auto\n\n- Removed the file skill-card.md.\n- No changes to functionality or documentation.\n- Maintenance update only.\n\nv1.1.4 | 2026-09-30T16:59:34.224Z | auto\n\n- Removed the file skill-card.md to streamline the skill package.\n- No changes to core functionality or behavior.\n\nv1.1.3 | 2026-09-25T15:51:37.184Z | auto\n\n- Removed the file skill-card.md.\n- No user-facing features or interface changes in this version.\n- Documentation and functionality remain unchanged.\n\nv1.1.2 | 2026-09-23T21:40:03.123Z | auto\n\n- Improved `homeAwayType` decoding: now recognizes `2` as \"neutral site\" and marks unknown values as `unknown`.\n- Documentation updates in SKILL.md to clarify `homeAwayType` and related edge cases.\n- Removed obsolete skill-card.md file for cleanup.\n- Reference docs (recipes.md) updated.\n\nv1.1.1 | 2026-09-23T15:45:05.826Z | auto\n\n- Removed the sample file skill-card.md.\n- No changes to code or documentation besides file cleanup.\n\nv1.1.0 | 2026-09-20T02:52:51.976Z | auto\n\n- Removed the file skill-card.md.\n- No functional or user-facing changes; this update is limited to documentation cleanup.\n\nv1.0.0 | 2026-09-19T11:18:30.936Z | auto\n\n- Removed the file skill-card.md.\n- No functional changes to the codebase.\n- Documentation and usage remain unchanged.\n\nv0.3.1 | 2026-09-10T17:50:23.041Z | auto\n\n- Removed the file skill-card.md.\n- No functional changes; documentation file removed only.\n\nv0.3.0 | 2026-09-04T22:21:14.668Z | auto\n\n- Removed the sample file skill-card.md.\n- No changes to code or functionality; this update only deletes an unused documentation file.\n\nv0.2.0 | 2026-08-01T20:20:53.685Z | auto\n\n- Adds support for leaderboards, rankings, and stat leader paths via new commands (`rankings`, `teamrankings`, `standings`, `statcats`, `statleaders`)\n- Documents leaderboard path structure and pagination in SKILL.md\n- Updates usage instructions and command list in SKILL.md and references/recipes.md\n- Removes outdated skill-card.md file\n\nv0.1.0 | 2026-08-01T19:17:10.177Z | auto\n\n- Initial release of the maxpreps skill, enabling access to US high school sports data from MaxPreps.com.\n- Allows searching for schools and athletes, and retrieving team schedules, scores, records, rosters, stats, rankings, and athlete career data.\n- Simple CLI workflow using Node.js script (`mpx.mjs`), requiring no authentication or API keys.\n- Supports flexible data retrieval with specific commands for each data type (schedule, roster, stats, etc.).\n- Handles MaxPreps' rapid buildId rotation, corrects for out-of-season/incomplete data, and filters out soft-deleted roster and contest entries by default.\n- Provides guidance for correctly resolving canonical team and season paths; supports fetching up to 20 years of sports history.\n\nArchive index:\n\nArchive v1.1.8: 5 files, 12752 bytes\n\nFiles: references/recipes.md (7495b), scripts/mpx.mjs (13437b), skill-card.md (1678b), SKILL.md (5056b), _meta.json (131b)\n\nFile v1.1.8:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away, `2` = neutral site** (decoded to `homeAway`; any other value → `unknown`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.8:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.8\",\n  \"publishedAt\": 1791588357242\n}\n\nFile v1.1.8:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away `2`=neutral · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.8:skill-card.md\n\n## Description:\n\nRead public MaxPreps high school sports data to find schools and explore team schedules, scores, rosters, statistics, rankings, and athlete careers in the United States.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nAgents serving sports researchers, journalists, and other users can look up public US high school team results, rosters, rankings, and athlete career information from MaxPreps.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The helper makes network requests to MaxPreps and retrieves public roster and athlete information.\n\nMitigation: Run it only when those read-only requests are acceptable, and handle retrieved athlete information with care.\n\n## Reference(s):\n\n- [ClawHub maxpreps-mcp release](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n- [MaxPreps](https://www.maxpreps.com)\n- [Usage recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Text or Markdown with sports data and optional shell examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [The included helper returns structured JSON for requested public MaxPreps data.]\n\n## Skill Version(s):\n\n1.1.8 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.7: 5 files, 12742 bytes\n\nFiles: references/recipes.md (7495b), scripts/mpx.mjs (13169b), skill-card.md (1996b), SKILL.md (5056b), _meta.json (131b)\n\nFile v1.1.7:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away, `2` = neutral site** (decoded to `homeAway`; any other value → `unknown`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.7:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.7\",\n  \"publishedAt\": 1791380215874\n}\n\nFile v1.1.7:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away `2`=neutral · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.7:skill-card.md\n\n## Description:\n\nRead MaxPreps.com high school sports data to find schools and retrieve team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers across the United States.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nAgents and their users look up public US high school sports information, including school teams, game results, rosters, rankings, and athlete careers.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Public roster and athlete results may contain information about students.\n\nMitigation: Limit lookups and shared outputs to relevant public sports information; avoid treating results as private records.\n\nRisk: Sports results may be incomplete or mistaken for official records.\n\nMitigation: Check the relevant season and verify important results against the original source before relying on them.\n\nRisk: Broad lookups can generate unnecessary requests to MaxPreps.\n\nMitigation: Use scoped school and team queries rather than broad scraping.\n\n## Reference(s):\n\n- [MaxPreps MCP on ClawHub](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n- [MaxPreps](https://www.maxpreps.com)\n- [MaxPreps recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [JSON, Text, Shell commands, Guidance]\n\n**Output Format:** [Structured JSON results and text or Markdown guidance with shell commands]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Results depend on publicly available MaxPreps data and the selected school, team, and season.]\n\n## Skill Version(s):\n\n1.1.7 (source: ClawHub release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.6: 5 files, 12658 bytes\n\nFiles: references/recipes.md (7495b), scripts/mpx.mjs (13169b), skill-card.md (1783b), SKILL.md (5056b), _meta.json (131b)\n\nFile v1.1.6:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away, `2` = neutral site** (decoded to `homeAway`; any other value → `unknown`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.6:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.6\",\n  \"publishedAt\": 1791168520687\n}\n\nFile v1.1.6:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away `2`=neutral · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.6:skill-card.md\n\n## Description:\n\nFind US high school sports teams and retrieve public MaxPreps schedules, scores, rosters, rankings, statistics, and athlete careers.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nAgents and their users can look up US high school sports teams and summarize public game results, team records, rosters, rankings, statistics, and athlete careers.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Running the included helper sends outbound requests to public MaxPreps/CBS-hosted pages.\n\nMitigation: Run it only when those outbound requests are acceptable and review the returned data before using it.\n\nRisk: An out-of-season team or a stale site build can make results appear missing.\n\nMitigation: Check an appropriate season and refresh or retry before concluding that records are unavailable.\n\n## Reference(s):\n\n- [MaxPreps skill on ClawHub](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n- [MaxPreps](https://www.maxpreps.com)\n- [Skill usage recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, JSON, Shell commands, Guidance]\n\n**Output Format:** [Markdown guidance and structured JSON sports data]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only public data; current-season records may be empty before games begin.]\n\n## Skill Version(s):\n\n1.1.6 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.5: 5 files, 12797 bytes\n\nFiles: references/recipes.md (7495b), scripts/mpx.mjs (13169b), skill-card.md (2064b), SKILL.md (5056b), _meta.json (131b)\n\nFile v1.1.5:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away, `2` = neutral site** (decoded to `homeAway`; any other value → `unknown`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.5:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.5\",\n  \"publishedAt\": 1790991933120\n}\n\nFile v1.1.5:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away `2`=neutral · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.5:skill-card.md\n\n## Description:\n\nRead MaxPreps.com high school sports data to find schools and retrieve team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers across the United States.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nAgents and their users can look up public US high-school sports information, including school teams, schedules, results, rosters, rankings, and athlete careers.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Rosters and athlete pages may contain public information about high-school students.\n\nMitigation: Avoid unnecessary collection or redistribution, and respect privacy expectations and site terms.\n\nRisk: Using the skill runs a bundled Node script that sends read-only requests to MaxPreps.\n\nMitigation: Install only if comfortable allowing the agent to run the script and make those requests.\n\nRisk: Out-of-season pages may have empty results, and some statewide scoreboards are not available through this skill.\n\nMitigation: Check a prior season or individual team schedules before concluding no games or athletes exist.\n\n## Reference(s):\n\n- [MaxPreps MCP on ClawHub](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n- [MaxPreps](https://www.maxpreps.com)\n- [Usage recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Shell commands, Guidance]\n\n**Output Format:** [Markdown guidance and shell examples; retrieved sports data is JSON]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only access to public sports data; availability and freshness depend on MaxPreps.]\n\n## Skill Version(s):\n\n1.1.5 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.4: 5 files, 12665 bytes\n\nFiles: references/recipes.md (7495b), scripts/mpx.mjs (13169b), skill-card.md (1805b), SKILL.md (5056b), _meta.json (131b)\n\nFile v1.1.4:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away, `2` = neutral site** (decoded to `homeAway`; any other value → `unknown`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.4:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.4\",\n  \"publishedAt\": 1790787574224\n}\n\nFile v1.1.4:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away `2`=neutral · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.4:skill-card.md\n\n## Description:\n\nRead MaxPreps high school sports data to find US schools, teams, schedules, scores, rosters, rankings, statistics, and athlete careers.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to look up public US high school sports schedules, scores, team records, rosters, rankings, statistics, and athlete histories.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Running the included helper makes network requests to MaxPreps and writes a small temporary cache.\n\nMitigation: Review the helper before execution and allow only the read-only MaxPreps access needed for the task.\n\nRisk: Public roster and athlete information may identify students.\n\nMitigation: Use athlete details only when necessary and avoid unnecessary bulk collection.\n\n## Reference(s):\n\n- [MaxPreps recipes](references/recipes.md)\n- [MaxPreps](https://www.maxpreps.com)\n- [ClawHub skill release](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Text or Markdown, with example shell commands and structured sports data]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Answers draw on public MaxPreps pages; current-season records may be empty before play begins.]\n\n## Skill Version(s):\n\n1.1.4 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.3: 5 files, 12653 bytes\n\nFiles: references/recipes.md (7495b), scripts/mpx.mjs (13169b), skill-card.md (1776b), SKILL.md (5056b), _meta.json (131b)\n\nFile v1.1.3:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away, `2` = neutral site** (decoded to `homeAway`; any other value → `unknown`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.3:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.3\",\n  \"publishedAt\": 1790351497184\n}\n\nFile v1.1.3:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away `2`=neutral · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.3:skill-card.md\n\n## Description:\n\nRead public MaxPreps high-school sports data to find schools and retrieve team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers across the United States.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and analysts use this skill to look up public U.S. high-school sports results, team details, rankings, and athlete profiles on MaxPreps.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Roster and athlete results can contain information about school-age athletes.\n\nMitigation: Use only public information and handle athlete details with appropriate privacy care.\n\nRisk: Broad or repeated requests to MaxPreps can impose unnecessary load.\n\nMitigation: Use targeted school and team lookups rather than broad scraping.\n\n## Reference(s):\n\n- [MaxPreps skill recipes](references/recipes.md)\n- [MaxPreps](https://www.maxpreps.com)\n- [ClawHub release](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Markdown responses with public sports data and optional shell commands]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Data is retrieved from public MaxPreps pages; no account or API key is required.]\n\n## Skill Version(s):\n\n1.1.3 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.2: 5 files, 12782 bytes\n\nFiles: references/recipes.md (7495b), scripts/mpx.mjs (13169b), skill-card.md (2065b), SKILL.md (5056b), _meta.json (131b)\n\nFile v1.1.2:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away, `2` = neutral site** (decoded to `homeAway`; any other value → `unknown`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.2:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.2\",\n  \"publishedAt\": 1790199603123\n}\n\nFile v1.1.2:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away `2`=neutral · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.2:skill-card.md\n\n## Description:\n\nRead MaxPreps.com high school sports data -- find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to query public MaxPreps high school sports data and guide agents through school lookup, team path resolution, schedules, scores, rosters, rankings, standings, stat leaders, and athlete career checks.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill makes live read-only requests to MaxPreps and, for troubleshooting recipes, MaxPreps asset hosts.\n\nMitigation: Use it only where those outbound public web requests are acceptable.\n\nRisk: Public sports data can be stale, out of season, or absent for a specific team path.\n\nMitigation: Resolve canonical school and team paths first, check prior seasons when current season data is empty, and cross-check decoded schedules or rosters before reporting conclusions.\n\n## Reference(s):\n\n- [MaxPreps recipes](references/recipes.md)\n- [MaxPreps](https://www.maxpreps.com)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, code, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with inline shell commands and JSON-producing CLI examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [The bundled CLI reads public MaxPreps pages, decodes Next.js JSON payloads, and writes only a temporary buildId cache.]\n\n## Skill Version(s):\n\n1.1.2 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.1: 5 files, 12751 bytes\n\nFiles: references/recipes.md (7483b), scripts/mpx.mjs (12930b), skill-card.md (2381b), SKILL.md (5005b), _meta.json (131b)\n\nFile v1.1.1:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away** (decoded to `homeAway`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.1:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.1\",\n  \"publishedAt\": 1790178305826\n}\n\nFile v1.1.1:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.1:skill-card.md\n\n## Description:\n\nRead MaxPreps.com high school sports data: find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and external users use this skill to help agents retrieve public MaxPreps school, team, roster, schedule, ranking, standings, stat leader, and athlete career data. It is most useful when an agent needs structured sports data and command examples instead of manual page inspection.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can surface public athlete, roster, and career details, including information about high school students.\n\nMitigation: Treat athlete details as privacy-sensitive, especially for minors, and avoid unnecessary redistribution or disclosure.\n\nRisk: Troubleshooting and recipe examples may include shell pipelines that fetch and process public web pages.\n\nMitigation: Review optional shell commands before running them and keep usage low-volume and read-only.\n\nRisk: Public MaxPreps page structures and seasonal data availability can change, which may produce empty, stale, or misaligned results.\n\nMitigation: Resolve school and team paths before fetching, check prior seasons when current-season data is empty, and verify important results against the source page.\n\n## Reference(s):\n\n- [MaxPreps recipes](references/recipes.md)\n- [MaxPreps public site](https://www.maxpreps.com)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with shell command examples and JSON command outputs]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only outputs depend on public MaxPreps pages and the site paths selected by the user.]\n\n## Skill Version(s):\n\n1.1.1 (source: server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.0: 5 files, 12643 bytes\n\nFiles: references/recipes.md (7483b), scripts/mpx.mjs (12930b), skill-card.md (2144b), SKILL.md (5005b), _meta.json (131b)\n\nFile v1.1.0:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away** (decoded to `homeAway`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.1.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.0\",\n  \"publishedAt\": 1789872771976\n}\n\nFile v1.1.0:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.nationalAverage)  \\(.path)\"'\n\n# 2. read one board\nnode \"$M\" statleaders nc/football/25-26/stat-leaders/offense/rushing/yds \\\n  | jq -r '.[0:15][] | \"\\(.rank)  \\(.name)  (\\(.schoolName), \\(.city))  \\(.stats[\"Rushing Yards\"])  \\(.teamPath)\"'\n```\n\nIndex 5 is **city** and index 6 is **school** in the raw tuple — `mpx` already maps them, but\nif you ever decode by hand, note the first row of a board can't tell you which is which when\nthe school is named after its town. If the shape drifts, `statleaders` returns\n`{warning, columns, rawRows}` rather than a mislabelled table.\n\n## Anything else\n\nAny public page has a JSON twin. Use `raw` and explore:\n\n```bash\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/standings | jq 'keys'\nnode \"$M\" raw nc/charlotte/myers-park-mustangs/football/25-26/rankings | jq '.rankingsData'\n```\n\nConfirmed extra tabs: `standings` (`standingsData`, `leaderStats`), `rankings`\n(`rankingsData`, `historicalRankingsData`). Both are plain named JSON — no decoding needed.\n\n---\n\n# Positional encoding\n\nTwo payloads ship as bare arrays with the field names stripped: `pageProps.athleteData`\n(roster) and `pageProps.contests` (schedule). The site rehydrates them with\n\n```js\ndeserializeObject(keys, row)  // a[keys[i]] = row[i]\n```\n\n`mpx.mjs` carries the three key lists lifted verbatim from the site bundle and applies the\nsame rule, **including the duplicate keys** in the team list (indices 25/27/28/31 repeat\nearlier names; the values are identical, so last-write-wins loses nothing).\n\n- `ROSTER_KEYS` — 37 fields, from `GSSP_ROSTER_SERIALIZE_KEYS`\n- `CONTEST_KEYS` — 41 fields, `TEAM_KEYS` — 32 fields, from `deserializeContestList`\n\nIf a decode ever comes out misaligned, the site changed its key list. Re-derive it:\n\n```bash\ncurl -s https://www.maxpreps.com/nc/charlotte/myers-park-mustangs/football/25-26/roster/ \\\n  | grep -oE 'src=\"https://asset\\.maxpreps\\.io/[^\"]+\\.js\"' | sed 's/src=\"//;s/\"$//' \\\n  | xargs -P8 -n1 curl -s | grep -ohE 'GSSP_ROSTER_SERIALIZE_KEYS\",0,\\[[^]]*\\]'\n```\n\nThe contest lists live in the schedule page's chunks under `deserializeContestList` —\nsearch those chunks for `let s=[\"id\",\"teamId\",\"sportSeasonId\"`.\n\n## Verifying a decode\n\nField alignment is silent when it breaks — a shifted key map still produces plausible JSON.\nCheck against something independent:\n\n- Roster row count must equal the rendered table's rows (63 for MP football 25-26; the\n  other 24 of 87 are `isDeleted`).\n- Summed per-game scores must equal `.standings.overallStanding.points` /\n  `.pointsAgainst` (412 / 141), and the W/L tally must equal `overallWinLossTies` (`9-3`).\n\nBoth held exactly when this skill was written.\n\n# Field notes\n\n- `homeAwayType` `0`=home `1`=away · `calculatedTeamContestResult` `2`=win `3`=loss\n- `classYear` `9`–`12`; `formattedClassYear` is empty in the payload, so `mpx` derives `classYearLabel`\n- `contestState` distinguishes scheduled / in-progress / boxscore / score-not-reported\n- `dateCode` flags TBA: date-TBA and time-TBA are separate states, so a `date` can be a placeholder — check `isDateTba` / `isTimeTba` before presenting a time\n- Contests carry `goFanUrl` / `nfhsStreamUrl` (tickets, streams) when the school is a partner\n- School ids, team ids, and sport-season ids are GUIDs; `careerid` in athlete URLs is a short base-36 code, not a GUID\n\nFile v1.1.0:skill-card.md\n\n## Description:\n\nRead MaxPreps.com high school sports data - find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users and developers use this skill to retrieve public MaxPreps high-school sports data and turn it into schedules, scores, rosters, rankings, standings, stat leaders, and athlete career summaries.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The local Node.js helper contacts public MaxPreps web endpoints and caches a temporary build identifier.\n\nMitigation: Install and run it only where outbound requests to MaxPreps and a temporary local cache are acceptable; review proposed commands before execution.\n\nRisk: Incorrect paths, stale build identifiers, or out-of-season pages can produce empty or misleading sports data.\n\nMitigation: Resolve schools and teams with the documented search and teams lookups, rely on the helper's build-id refresh, and cross-check important schedules or rosters against source totals.\n\n## Reference(s):\n\n- [MaxPreps recipes](references/recipes.md)\n- [MaxPreps](https://www.maxpreps.com)\n- [ClawHub skill page](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown guidance with shell command examples and JSON command output]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Uses a local Node.js helper to fetch public MaxPreps JSON routes and decode sports data; no MaxPreps credentials or browser session are required.]\n\n## Skill Version(s):\n\n1.1.0 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.0.0: 5 files, 12551 bytes\n\nFiles: references/recipes.md (7483b), scripts/mpx.mjs (12930b), skill-card.md (1985b), SKILL.md (5005b), _meta.json (131b)\n\nFile v1.0.0:SKILL.md\n\n---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 years of history; `mpx teams <team-path>` lists\nevery season with its `year`, or read `.seasons` from `mpx team <team-path>`.\n\n## Gotchas\n\n- **`buildId` rotates on every deploy.** `mpx` caches it for 6h and silently re-resolves on a 404, so this is handled — but a hand-rolled `curl` against a stale id 404s. Use the script, or `node \"$M\" buildid` first.\n- **An out-of-season team looks broken.** Before opening day the current season has an empty roster and no results. That is correct data, not a failure — check the prior season (`25-26`) before reporting nothing.\n- **`isDeleted` rows are real and numerous.** The 25-26 Myers Park football roster carries 87 entries, 63 of which the site renders; the rest are soft-deleted duplicates. `mpx` filters them by default (matching the site) — `--all` keeps them. Same for contests.\n- **`resultString` is winner-first, not team-first.** A loss reads `\"L 20-13\"` even though the team scored 13. For team-vs-opponent use the decoded `teamScore` / `opponentScore` fields, which `mpx` orients correctly.\n- **`homeAwayType`: `0` = home, `1` = away** (decoded to `homeAway`).\n- **`classYear` is a number**: 9–12, decoded to `classYearLabel` (`Fr.`/`So.`/`Jr.`/`Sr.`).\n- **Statewide scoreboards are not in the JSON.** `/<st>/<sport>/scores/` returns only page chrome — the game list is hydrated by a route that never fires server-side. Get scores per-team from `schedule` instead; don't claim a state had no games.\n- Search is strict — `\"myers park high\"` returns zero, `\"myers park\"` returns the school. Drop qualifiers and retry before concluding a school is absent.\n\nRead-only and low-volume by design; there is no write path. See `references/recipes.md` for\nfield shapes, the positional key maps, and ready-to-run jq.\n\nFor the same data as typed MCP tools — usable from claude.ai or any client without this CLI —\nsee [maxpreps-mcp](https://github.com/chrischall/maxpreps-mcp), which ships this skill.\n\nFile v1.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.0.0\",\n  \"publishedAt\": 1789816710936\n}\n\nFile v1.0.0:references/recipes.md\n\n# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national instead of one state: drop the state segment\nnode \"$M\" rankings football/25-26/rankings/1 | jq -r '.teams[] | \"\\(.rank) \\(.schoolFormattedName)\"'\n\n# where does one team rank?\nnode \"$M\" teamrankings nc/charlotte/myers-park-mustangs/football/25-26/rankings \\\n  | jq -r '.[] | \"\\(.contextName): \\(.nearby[] | select(.schoolId==\"327115b3-501d-4047-aee8-ff08e6b35bd9\") | .rank)\"'\n\n# the conference table\nnode \"$M\" standings nc/charlotte/myers-park-mustangs/football/25-26/standings \\\n  | jq -r '.sections[] | .name, (.teams[] | \"  \\(.conferenceStandingPlacement // \"-\")  \\(.schoolName)  conf \\(.conferenceWinLossTies)  overall \\(.overallWinLossTies)\")'\n```\n\n`teamPath` on each ranking entry is already stripped to a plain team path, so it\nfeeds straight into `schedule` / `roster` / `team`.\n\nAn out-of-season leaderboard returns `totalCount: 0` — that is the season not having\nstarted, not an empty state. Pass an earlier season.\n\n## Stat leaderboards\n\n```bash\n# 1. list the categories — leaf paths are NOT derivable from the stat name\nnode \"$M\" statcats nc/football/25-26/stat-leaders | jq -r '.[] | \"\\(.statName)  natAvg=\\(.","readmeExcerpt":"Skill: maxpreps-mcp Owner: chrischall Summary: Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school. Tags: latest:1.1.8 Version history: v1.1.8 | 2026-10-09T23:25:57.242Z | auto maxpreps-mcp v1.1.8 - Removed skill-card.md, consolidating documentation into SKILL.md. - No changes to functionality;","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule"},{"language":"bash","snippet":"# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'"},{"language":"bash","snippet":"# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'"},{"language":"bash","snippet":"S=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'"},{"language":"bash","snippet":"node \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }"},{"language":"bash","snippet":"R=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: maxpreps\ndescription: \"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school.\"\n---\n\n# MaxPreps\n\nNational high-school sports database (CBS Sports). **No auth, no API key, no browser bridge** — plain `curl` from anywhere, no signed-in tab.\n\nPages are Next.js SSR: every public page has a companion JSON route at\n`/_next/data/<buildId>/<path>.json`. `scripts/mpx.mjs` resolves the `buildId`, fetches, and decodes.\n\n```bash\n# scripts/mpx.mjs sits next to this SKILL.md — point M at it.\n# Standalone install: M=~/.claude/skills/maxpreps/scripts/mpx.mjs\n# Shipped with maxpreps-mcp: M=<plugin-root>/skills/maxpreps/scripts/mpx.mjs\nM=~/.claude/skills/maxpreps/scripts/mpx.mjs\n\nnode \"$M\" search \"myers park\"\nnode \"$M\" schedule nc/charlotte/myers-park-mustangs/football/25-26/schedule\n```\n\n`mpx.mjs <kind> [path]` — `path` is the site path without leading/trailing slashes.\nWith no path it decodes a payload on stdin. Kinds:\n\n- `search \"<query>\"` — schools + athletes (**start here**)\n- `teams <school-or-team-path>` — every team path the school publishes\n- `schedule` — games with scores, result, home/away (decodes the positional array)\n- `roster` — players with jersey, position, height, class (decodes the positional array)\n- `stats` — team stat leaders · `team` — season info, record, standings, rankings\n- `rankings` — ranked leaderboard for a sport (see path note below)\n- `teamrankings` / `standings` — one team's ranks; its conference table\n- `statcats` / `statleaders` — stat leaderboard index; one board's ranked athletes\n- `school` — school profile + nearby schools · `athlete` — one athlete's career\n- `raw` — undecoded `pageProps`, for anything without a dedicated kind\n- `buildid` — print the cached build id\n\nFlags: `--all` keep `isDeleted` rows · `--raw` emit undecoded `pageProps` · `--fresh` (buildid) bypass cache.\n\n## Resolve before you fetch\n\nDon't guess paths — two lookups, both cheap:\n\n```bash\n# 1. school -> canonicalUrl\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# 2. school path -> real team paths\nnode \"$M\" teams ca/santa-ana/mater-dei-monarchs | jq -r '.[] | \"\\(.path)  [\\(.gender) \\(.sport) \\(.level)]\"'\n```\n\nTeam path grammar is `<sport>[/girls][/jv|/freshman][/<yy-yy>]/<tab>`, but the segments are\n**not predictable** — the default gender varies by sport (`golf/girls` + `golf/spring`,\n`field-hockey` is Girls with no gender segment). Always take paths from `teams`.\n\nTabs: `schedule`, `roster`, `stats`, `rankings`, `standings`. Omit the tab for the team home page.\n\nLeaderboards live on their own path: `[<st>/]<sport>[/<season>]/rankings/<page>` — the trailing\npage number is **required** (omitting it 404s) and each page holds 25 teams.\n\n## Seasons\n\nCurrent season = no year segment. Prior seasons insert `<yy-yy>` **before** the tab:\n`.../football/25-26/schedule`. Roughly 20 "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn700jq4sjtf2anb0rk3ft4p7n856872\",\n  \"slug\": \"maxpreps-mcp\",\n  \"version\": \"1.1.8\",\n  \"publishedAt\": 1791588357242\n}"},{"path":"references/recipes.md","content":"# MaxPreps recipes\n\n`M=~/.claude/skills/maxpreps/scripts/mpx.mjs`\n\n## Find things\n\n```bash\n# schools\nnode \"$M\" search \"mater dei\" | jq -r '.schools[] | \"\\(.name) (\\(.city), \\(.state))  \\(.canonicalUrl)\"'\n\n# athletes — careerCanonicalUrl already carries the ?careerid= the athlete page needs\nnode \"$M\" search \"brody keefe\" | jq -r '.athletes[] | \"\\(.fullName)  \\(.schoolFormattedName)  \\(.sports|join(\",\"))  \\(.careerCanonicalUrl)\"'\n\n# a school's teams, varsity only\nnode \"$M\" teams nc/charlotte/myers-park-mustangs \\\n  | jq -r '.[] | select(.level==\"Varsity\") | \"\\(.path)  [\\(.gender) \\(.sport)]\"'\n\n# every season of one team, newest first\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football \\\n  | jq -r '.seasons[] | select(.level==\"Varsity\") | \"\\(.year)  \\(.canonicalUrl)\"'\n```\n\n## Schedule and scores\n\n```bash\nS=nc/charlotte/myers-park-mustangs/football/25-26/schedule\n\n# one line per game\nnode \"$M\" schedule $S | jq -r '.[] | \"\\(.date[0:10])  \\(.homeAway)  \\(.result // \"—\")  \\(.teamScore)-\\(.opponentScore)  \\(.opponent)\"'\n\n# record + point differential computed from games\nnode \"$M\" schedule $S | jq '{\n  W: [.[]|select(.result==\"W\")]|length,\n  L: [.[]|select(.result==\"L\")]|length,\n  pf: [.[].teamScore]|add, pa: [.[].opponentScore]|add }'\n\n# upcoming only\nnode \"$M\" schedule $S | jq '[.[] | select(.hasResult|not)]'\n```\n\nCross-check against the site's own totals — they must agree:\n\n```bash\nnode \"$M\" team nc/charlotte/myers-park-mustangs/football/25-26 | jq '.standings.overallStanding'\n# { overallWinLossTies: \"9-3\", points: 412, pointsAgainst: 141, homeWinLossTies: \"4-1\", ... }\n```\n\n`.standings.leagueStanding` adds `leagueName`, `conferenceWinLossTies`, `conferenceStandingPlacement`.\n\n## Roster\n\n```bash\nR=nc/charlotte/myers-park-mustangs/football/25-26/roster\n\nnode \"$M\" roster $R | jq -r '.[] | \"#\\(.jersey)  \\(.name)  \\(.classYearLabel)  \\(.positions)  \\(.height // \"—\")  \\(.weight // \"—\") lbs\"'\n\nnode \"$M\" roster $R | jq 'group_by(.classYearLabel) | map({class: .[0].classYearLabel, n: length})'\nnode \"$M\" roster $R | jq '[.[] | select(.hasStats)] | length'   # who has stats pages\n```\n\n## Stat leaders\n\n```bash\nnode \"$M\" stats nc/charlotte/myers-park-mustangs/football/25-26/stats \\\n  | jq -r '.leaders[] | \"\\(.athleteFirstName) \\(.athleteLastName)  \\(.stat.displayName): \\(.stat.value)\"'\n```\n\n`.minimums` lists the qualifying thresholds; `.lastUpdated` is the stat refresh time.\n\n## Athlete career\n\n```bash\nnode \"$M\" athlete \"nc/charlotte/myers-park-mustangs/athletes/brody-keefe?careerid=c35dcsgih39sc\" \\\n  | jq '{name, availability}'\n```\n\n`.history` is an array of season entries; `.cards` holds the rendered career highlights.\n\n## Rankings and standings\n\n```bash\n# leaderboard: [<st>/]<sport>[/<season>]/rankings/<page>  — page number required, 25 per page\nnode \"$M\" rankings nc/football/25-26/rankings/1 \\\n  | jq -r '\"\\(.totalCount) teams, updated \\(.lastUpdated)\", (.teams[] | \"\\(.rank)  \\(.schoolFormattedName)  \\(.overall)  \\(.rating)  \\(.teamPath)\")'\n\n# national inst"},{"path":"skill-card.md","content":"## Description:\n\nRead public MaxPreps high school sports data to find schools and explore team schedules, scores, rosters, statistics, rankings, and athlete careers in the United States.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[chrischall](https://clawhub.ai/user/chrischall)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nAgents serving sports researchers, journalists, and other users can look up public US high school team results, rosters, rankings, and athlete career information from MaxPreps.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The helper makes network requests to MaxPreps and retrieves public roster and athlete information.\n\nMitigation: Run it only when those read-only requests are acceptable, and handle retrieved athlete information with care.\n\n## Reference(s):\n\n- [ClawHub maxpreps-mcp release](https://clawhub.ai/chrischall/skills/maxpreps-mcp)\n- [MaxPreps](https://www.maxpreps.com)\n- [Usage recipes](references/recipes.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Guidance]\n\n**Output Format:** [Text or Markdown with sports data and optional shell examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [The included helper returns structured JSON for requested public MaxPreps data.]\n\n## Skill Version(s):\n\n1.1.8 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school. Skill: maxpreps-mcp Owner: chrischall Summary: Read MaxPreps.com high school sports data — find a school, then get team schedules, scores, records, rosters, stat leaders, rankings, and athlete careers for any US high school. Tags: latest:1.1.8 Version history: v1.1.8 | 2026-10-09T23:25:57.242Z | auto maxpreps-mcp v1.1.8 - Removed skill-card.md, consolidating documentation into SKILL.md. - No changes to functionality;","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1200,"uniquenessScore":53,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T11:29:15.681Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T11:29:15.681Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T14:14:51.411Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}