Skip to main content

MCP Server

Pawtograder exposes a Model Context Protocol (MCP) server as a Supabase Edge Function. This page covers the implementation: how authentication works, what the RLS guarantees are, what tools are available, and how to extend the server. For instructor-facing setup (connecting Claude Desktop, creating API tokens, using the AI Help button), see AI assistance for helping students.

Architecture

The MCP server is a Supabase Edge Function that speaks streamable HTTP MCP. In production it is reachable at:
<SUPABASE_URL>/functions/v1/mcp-server
When running Supabase locally, the function is served under the local Edge Function URL printed by npx supabase start (the Supabase CLI also prints a convenience MCP URL at http://127.0.0.1:54321/mcp). The function shares types and helpers with the rest of Pawtograder’s edge functions under supabase/functions/_shared/. Database queries go through the standard typed Supabase server client, so every query respects the same Postgres row-level security policies the rest of the app uses.

Authentication

The server accepts long-lived JWTs issued by Pawtograder’s API token system (Settings → API Tokens) in the Authorization: Bearer <token> header. Token properties:
  • Issued and signed by Pawtograder; carry the issuing user’s identity.
  • Scoped to MCP read access (mcp:read).
  • Revocable from the UI; revocation is checked on every request.
  • Tracked with a last_used_at timestamp for audit.
A request that arrives without a valid, non-revoked token is rejected with 401.

Authorization and RLS

Authorization happens in two layers:
  1. Role gate – Before any tool runs, the server checks that the authenticated user has instructor or grader role in the class targeted by the request. Students cannot use the MCP server, even with a valid token.
  2. Postgres RLS – Every database query runs as the authenticated user, so the same RLS policies that govern the web app also govern MCP responses. There is no service-role escalation inside tool handlers.
The server is designed to never leak data that the same user couldn’t see in the web app:
  • The users table is never queried directly from tool handlers.
  • The is_private_profile flag is not exposed in any tool response.
  • All access goes through views and tables already guarded by RLS.

Available tools

The full tool list is defined in the edge function source. At time of writing it covers: Context
  • get_help_request — help request plus full chat history
  • get_discussion_thread — thread plus replies
  • get_assignment — assignment spec, handout_url, and rubric
Submissions
  • get_submission, list_submission_files, get_submission_files
  • list_submission_tests, get_test_output, get_submission_build_output
Repositories
  • list_grader_files, get_grader_files
  • list_handout_files, get_handout_files
Search
  • search_help_requests, search_discussion_threads
  • get_submissions_for_student
The authoritative list lives next to the handler implementations; treat this section as a quick reference, not a contract.

Assignment handout context

Assignments carry an optional handout_url column that points to the spec students see. Several tools (notably get_assignment and the help request / thread context tools) include handout_url in their response so that an AI assistant can fetch the assignment description without needing additional credentials.

Extension points

Common reasons to extend the MCP server:
  • Adding a tool — Define the tool descriptor (name, parameters, description) and implement the handler. Read data through the authenticated Supabase client so RLS is enforced automatically. Add the tool to the registry so it shows up in tools/list.
  • Expanding context — When you add fields to existing tools, prefer composing existing queries over loosening RLS. If a new field requires privileged access, surface it through a dedicated view with explicit RLS rather than service-role queries inside the handler.
  • New auth scopes — The API token system supports multiple scopes. If a tool needs to mutate state (e.g. posting a draft response), add a new scope rather than overloading mcp:read.

Troubleshooting

401 Unauthorized

  • Token revoked, expired, or malformed.
  • Missing Authorization: Bearer header.

403 Forbidden

  • The user is authenticated but not an instructor or grader in the class the request targets.

404 from a tool with no data

  • The query succeeded but RLS filtered the result to empty. Verify the user really has access in the web app.

Local server not reachable

  • Confirm Supabase is running: npx supabase status.
  • Confirm the edge function is being served (the Supabase CLI runs edge functions automatically when you start it; if you are using npx supabase functions serve manually, make sure mcp-server is in the served set).

Contributing

The MCP server lives under supabase/functions/mcp-server/ in the platform repo. When changing tool signatures, regenerate any shared types (npm run client-local) and update the staff-facing docs in staff/ai-assistance.mdx if the tool list or setup flow changes.