Skip to main content
Glama

powerschool-mcp

A read-only MCP server that lets Claude read a student's PowerSchool guardian portal: current grades, assignments and missing work, attendance, schedule, grade history, teacher comments, plus a few computed extras (what-if grades and an estimated GPA).

It cannot change anything in PowerSchool. Every request goes through a strict allowlist (see Read-only allowlist).

How it works

PowerSchool sign-in is district SSO, which a server can't do. So login happens on your PC:

  1. ps-login opens a real browser, you sign in, and it extracts only the two session cookies (JSESSIONID, psaid).

  2. It either saves them to .auth/session.json (local stdio mode) or POSTs them to your deployed server.

  3. The server uses plain HTTPS requests with those cookies and keeps the session alive with a page request every 10 minutes (idle timeout is about 40 minutes).

When the session dies, tools return session_expired and tell you to run ps-login again.

Related MCP server: io.github.thehesiod/psquare

Tools

Tool

What it does

session_status

Logged in or not, seconds of idle time left, active student. Does not extend the session.

list_students

Students on the account and the index to pass as student.

get_grades

Current letter grade and percent per class (home page), absences, tardies.

get_class_scores

Assignments for one class: score, points, percent, due date, category, flags (missing, late, exempt, collected). course matches by fuzzy name or period.

get_missing

All missing or late assignments across classes.

get_attendance

Absences and tardies from the attendance plugin, with an HTML fallback. Optional from / to.

get_schedule

Weekly bell schedule plus the period matrix (teacher, room, term).

get_grade_history

Stored final grades for every term on file.

get_teacher_comments

Teacher comments per class for a term.

estimate_gpa

Estimated GPA on a 4.0 scale from grade history; weighted=true adds +1 for course names containing AP or Honors. Always labelled as an estimate, not the official GPA.

what_if_grade

Recomputes a class percent from the real assignment data after hypothetical scores. Points-based; states its assumptions.

Every tool takes an optional student (name or index). It defaults to PS_DEFAULT_STUDENT, then to the student PowerSchool is showing.

Multiple students: grade pages only ever show the student PowerSchool has active, and the portal's student-switch mechanism could not be found on the account used for recon (see docs/auth-notes.md). Asking for a different student returns a clear error instead of guessing.

Setup (local, stdio, Claude Desktop)

Needs Node 20+.

npm install
npx playwright install chromium     # one-time, for ps-login only
copy .env.example .env              # set PS_BASE_URL, optionally PS_DEFAULT_STUDENT
npm run build
npm run login                       # opens a browser, sign in via SSO; saves .auth/session.json

Claude Desktop config (Settings -> Developer -> Edit Config), then restart Claude Desktop fully:

{
  "mcpServers": {
    "powerschool": {
      "command": "node",
      "args": ["C:\\path\\to\\powerschoolMCP\\dist\\main.js"],
      "env": {
        "PS_BASE_URL": "https://ps.yourdistrict.org",
        "PS_DEFAULT_STUDENT": ""
      }
    }
  }
}

The stdio server reads .auth/session.json relative to its working directory. Claude Desktop starts servers from its own directory, so pass an absolute path with "--session-file":

"args": ["C:\\path\\to\\powerschoolMCP\\dist\\main.js", "--session-file", "C:\\path\\to\\powerschoolMCP\\.auth\\session.json"]

It re-reads the file whenever ps-login --local rewrites it, so you don't need to restart anything after logging in again.

Deploy (Coolify / Docker)

openssl rand -hex 24    # run twice: one for MCP_SECRET, one for SESSION_PUSH_SECRET
  1. Coolify: New Resource -> Dockerfile from this repo (or any Docker host). No Playwright in the image.

  2. Environment variables:

    Variable

    Value

    PS_BASE_URL

    https://ps.yourdistrict.org

    MCP_SECRET

    random, at least 24 chars. Becomes the MCP URL path.

    SESSION_PUSH_SECRET

    random, at least 24 chars, different from MCP_SECRET

    PS_DEFAULT_STUDENT

    optional

    PORT

    3417 (default)

  3. The container listens on port 3417. Health check: GET /health always returns 200, even with no PowerSchool session, so a logged-out server never fails its deploy.

  4. Expose it on a subdomain. Don't put it behind Cloudflare Access or Basic Auth (claude.ai can't pass those); the secret path is the protection.

  5. Log in from your PC: npm run login:server -- --server https://ps-mcp.yourdomain.com --secret <SESSION_PUSH_SECRET> (or set PUBLIC_URL and SESSION_PUSH_SECRET in .env and run npm run login:server).

  6. In claude.ai: Settings -> Connectors -> add custom connector -> https://ps-mcp.yourdomain.com/mcp/<MCP_SECRET>.

The session lives in server memory only. If the container restarts, run ps-login again.

ps-login

npx tsx src/cli/ps-login.ts [--local] [--server https://...] [--secret ...]
  • Opens headed Chromium with the persistent .auth/profile profile and waits up to 5 minutes for /guardian/home.html.

  • Extracts only JSESSIONID and psaid; cookie values are never printed.

  • --local writes .auth/session.json. --server POSTs {jsessionid, psaid} to <server>/session with the X-Session-Secret header; the server checks them against PowerSchool (seconds-remaining) before accepting and answers ok or fail.

  • During login the browser only reaches PowerSchool's sign-in pages; afterwards the same read-only allowlist applies.

npm run login is --local; npm run login:server pushes to a server.

Configuration

Env var

Flag

Default

PS_BASE_URL

--url

(required)

PS_DEFAULT_STUDENT

--student

the active student

MCP_SECRET

--secret

required for --http, 24+ chars

SESSION_PUSH_SECRET

--push-secret

required for --http, 24+ chars

PORT / HOST

--port / --host

3417 / 127.0.0.1 (the Docker image sets HOST=0.0.0.0)

PS_KEEPALIVE_MIN

--keepalive-min

10 (0 disables)

PS_CACHE_SEC

--cache-sec

300

PUBLIC_URL

used by ps-login --server when --server is omitted

Security notes

  • Secrets only in .env and .auth/, both gitignored. Nothing in this repo contains cookies, tokens, grades, names or student identifiers; test fixtures are synthetic.

  • Remote auth: the MCP endpoint is /mcp/<MCP_SECRET> (same approach as canvas-mcp). POST /session needs the X-Session-Secret header. Both compare in constant time, both are rate-limited after repeated failures, and the two secrets must differ. /health is the only unauthenticated route and reveals nothing.

  • Treat the MCP URL like a password. Anyone with it can read the student's grades. If it leaks, change MCP_SECRET and redeploy.

  • Cookies are held in memory on the server and never logged or returned by any tool. list_students hides student identifiers.

  • Run it over HTTPS only. ps-login refuses a non-HTTPS --server unless it is localhost.

  • The session cookies are as powerful as your portal login: keep .auth/ private and don't share it.

  • Use it for the student's own family account. Respect your district's acceptable-use policy.

Read-only allowlist

Every PowerSchool request, from the server, from ps-login and from the tests, goes through assertAllowed() in src/ps/allowlist.ts. It is an allowlist, not a blocklist: anything not listed throws before any network call.

Allowed:

  • GET of /guardian/ home, scores, termgrades, teachercomments, myschedule*, attendance* pages (home also serves as the keep-alive request)

  • POST /ws/xte/assignment/lookup (the read-only lookup the portal's own score pages send)

  • GET /ws/xte/section/id/<n>/setting_enabled/

  • GET /ws/session/seconds-remaining (reports idle time, does not extend it)

  • POST /guardian/mba_attendance_monitor/json/*.json (attendance plugin reads)

Always rejected, even if a path would otherwise match: any URL (path, query or fragment, also URL-encoded) containing logoff, logout, signout, signoff, singlelogout or password, and any ac= query parameter. Requests to other hosts and credentials in URLs are rejected too. This exists because the Phase 1 crawl once followed home.html?ac=logoff and ended its own session. PUT /ws/session/last-hit is deliberately not on the list.

Development

npm run dev          # stdio, from source
npm run dev:http     # HTTP, from source
npm run typecheck
npm test             # vitest: allowlist, parsers, session expiry, MCP over HTTP

Tests use synthetic fixtures in test/fixtures/. Extra parser tests run against recon-output/raw when that folder exists and are skipped otherwise.

Limitations

  • HTML pages are scraped, so a PowerSchool template change can break a parser.

  • Category weights are not exposed by PowerSchool; what_if_grade is points-based unless you pass weights.

  • Stored-only terms (E1, E2, closed S2) usually have no assignment data.

  • Only the active student's grade pages can be read (see above).

  • The attendance plugin is third-party and district-installed; the HTML grid is used if it fails.

MIT licensed.

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    Enables natural language queries about grades, GPA, attendance, and schedules by connecting Claude to StudentVue school dashboards. Includes analytical tools for grade simulation, what-if scenarios, and academic performance tracking based on live school data.
    19
    -
  • A
    license
    A
    quality
    F
    maintenance
    Enables Claude to access the ParentSquare school-parent communication platform via its web interface and internal APIs, covering parent features like feeds, calendars, messages, forms, and payments, plus school admin roster management such as students, guardians, classes, and staff.
    48
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables Claude to access school information system data such as timetables, grades, homework, messages, students, teachers, and meal information, including meal ratings.
    26
    GPL 3.0