Skip to content

Initial mcp support - #3000

Open
AlreadyBored wants to merge 29 commits into
masterfrom
alreadybored/initial-mcp-support
Open

Initial mcp support#3000
AlreadyBored wants to merge 29 commits into
masterfrom
alreadybored/initial-mcp-support

Conversation

@AlreadyBored

@AlreadyBored AlreadyBored commented May 22, 2026

Copy link
Copy Markdown
Contributor

Pull Request Guidelines

Issue:
#2887

Description:

Adds @rsschool/mcp-server — an MCP (Model Context Protocol) server that lets AI agents (Claude Desktop, Cursor, Codex, GitHub Copilot) perform RS School actions on behalf of an authenticated user, strictly scoped to that user's roles.

How it works

  • Standalone mcp/ package, deployed as a compose container behind nginx at /mcp; also runnable over stdio for local clients.
  • Auth is a Personal Access Token (rsapp_pat_…) generated in RS School. Identity and per-course roles are resolved via the backend /session; the agent inherits the PAT owner's permissions and nothing more.
  • Stateless streamable-HTTP transport (plus stdio). Each request gets a fresh server instance whose tool list is filtered to the caller's roles.
  • 49 tools across 6 toolsets (common / student / mentor / course-management / course-admin / users). Tool visibility is role-filtered and can be narrowed further via RSAPP_TOOLSETS. Write/destructive tools are described to require explicit user confirmation.

Backend

  • PAT authentication strategy: tokens hashed at rest (sha256), constant-time compare, expiry + revocation, prefix lookup.
  • New migration 1779465349517-PersonalAccessTokenAndAuditLog (PAT storage + audit log).

Security hardening (pre-production review)

A full pass over the MCP attack surface, across the NestJS backend and the MCP server.

Authorization — cross-course IDOR fixes (backend)

The root cause was authorization by "role in any course": the MCP union-role gate plus RoleGuard defaulting to requireCourseMatch=false, with tools forwarding courseId/githubId verbatim.

  • expel_students, createCourseTask, copyFrom (schedule), copyCourse, and the mentor dashboard/students-count/random-task routes now require the role in the target course (requireCourseMatch: true) — a role in one course no longer grants the action in another.
  • getStudentSummary was DefaultGuard-only (any authenticated user could read any student's score + mentor contacts). Now CourseGuard + an in-body staff-or-self check — the student dashboard still reads its own summary.
  • getCourseStats: the canAccessCourse check was dead code (async, never awaited) — now awaited.
  • searchCourseStudents: added a course-scoped role gate.
  • RoleGuard hardened to deny instead of silently falling back to the any-course check when a course match is required but no courseId resolves.

Authorization — MCP-layer defense-in-depth

  • create-server: a centralized per-course role check, so a course-scoped tool can't be invoked against a course where the caller lacks the role — even if a backend guard is misconfigured.
  • search_users: includeSystem enforced as admin-only.
  • approve_mentor: preselected courses scoped to ones the caller manages/supervises.

Transport / output / input

  • DNS-rebinding protection with host/origin allow-list, opt-in via RSAPP_ALLOWED_HOSTS / RSAPP_ALLOWED_ORIGINS; CORS allow-list replaces the wildcard *.
  • Tool output strips secret-like keys and the mentor's personal contacts from get_student_summary; backend 5xx bodies collapse to a generic message.
  • Input bounds on arrays and free-text fields; grant_course_roles refuses a call that names no role (which would revoke everything).

Docs: documented the nginx /mcp rate-limit requirement (the container bypasses the nginx /api/v2 layer) and PAT rotation.

Tests: MCP test:ci — 257 tests @ 100% coverage; NestJS — 2070 tests @ ~96% coverage. ESLint + oxfmt clean.

Known follow-ups (not in this PR): searchMentors role enforcement, registry cancel/comment course-scoping, and the client-side limit in the list-details tools.

Self-Check:

  • Database migration added (PAT + audit log)
  • Changes tested locally

AlreadyBored and others added 20 commits May 22, 2026 18:01
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both the PR and master defined a class named CertificateCriteriaDto; swagger
registers schemas by class name, so the stricter PR variant overwrote the
optional-fields master variant in spec.json and broke the client build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Close cross-course authorization holes reachable through the MCP tools and
add defense-in-depth across the transport, output, and input layers.

Authorization (backend):
- expel_students, createCourseTask, copyFrom, copyCourse and the mentor
  dashboard/count/random-task routes: add requireCourseMatch so a role in
  one course no longer grants the action in another (cross-course IDOR)
- getStudentSummary: was DefaultGuard-only; add CourseGuard + in-body
  staff-or-self check (students still read their own summary)
- getCourseStats: await the canAccessCourse check (was dead code)
- searchCourseStudents: add course-scoped role gate
- RoleGuard: deny instead of falling back to any-course when
  requireCourseMatch is set but no courseId resolves

Authorization (MCP layer, defense-in-depth):
- create-server: per-course role check for any course-scoped tool
- search_users: enforce includeSystem as admin-only
- approve_mentor: scope preselected courses to ones the caller manages

Transport: DNS-rebinding protection + host/origin allow-list (opt-in via
RSAPP_ALLOWED_HOSTS/RSAPP_ALLOWED_ORIGINS); CORS allow-list replaces `*`.

Output: redact secret-like keys from tool output; strip mentor contacts
from get_student_summary; collapse backend 5xx bodies to a generic message.

Input bounds: cap submit_multiple_scores array, submit_interview_feedback
json size, approve_mentor courses, and free-text fields; grant_course_roles
refuses a call that names no role (would revoke all).

Docs: note the nginx /mcp rate-limit requirement and PAT rotation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The backend PUT /courses/:courseId/tasks/:courseTaskId requires
studentStartDate and studentEndDate on every call (both are @isnotempty
in UpdateCourseTaskDto), so a partial update that changed only e.g.
maxScore was rejected with HTTP 400 — contradicting the tool's promise
that "only the provided fields are changed".

Now, when either date is omitted, the tool fetches the current task
(GET /courses/:courseId/tasks/:courseTaskId) and carries the existing
dates over, so callers can change a single field. The success message
still reports only the fields the caller actually changed.

Found during a full functional pass over the MCP tools against a local
backend. Verified end-to-end: a maxScore-only update now succeeds and
preserves the existing dates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial MCP support to RSSchool App: new @rsschool/mcp-server workspace plus backend + UI changes to support Personal Access Tokens (PAT), audit logging, and tighter course-scoped authorization so AI agents can act on behalf of an authenticated user within their real permissions.

Changes:

  • Add PAT auth (bearer) + system-user support + audit log to NestJS, plus multiple guard hardening changes to prevent cross-course IDOR
  • Add new mcp/ workspace (stdio + streamable HTTP) exposing RS School tools with role-filtered toolsets, output redaction, and input bounds
  • Add infra wiring: nginx /mcp proxy + compose service + deploy workflow job; add UI entry points for admin/system users, tokens, audit log

Reviewed changes

Copilot reviewed 216 out of 217 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
turbo.json Add MCP-related env vars to Turbo cache/env passthrough
setup/nginx/nginx.conf Add /mcp upstream + rate limiting + proxy route
package.json Add mcp workspace
nestjs/src/users/users.module.ts Register SystemUsersController
nestjs/src/users/users.controller.ts Add includeSystem query flag with admin-only enforcement
nestjs/src/users/users-extra.spec.ts Update searchUsers expectations for new includeSystem option
nestjs/src/users/system-users.controller.ts Add admin-only CRUD endpoints for system users
nestjs/src/users/dto/create-system-user.dto.ts Add DTO + validation for creating system users
nestjs/src/users/dto/update-system-user.dto.ts Add DTO + validation for updating system user name
nestjs/src/users/dto/system-user.dto.ts Add DTO for system user list/create/update responses
nestjs/src/users/dto/index.ts Export new system-user DTOs
nestjs/src/personal-access-tokens/personal-access-tokens.service.spec.ts Add unit tests for token parsing + hashing helpers
nestjs/src/personal-access-tokens/personal-access-tokens.module.ts Add PAT module wiring
nestjs/src/personal-access-tokens/dto/personal-access-token.dto.ts Add DTO for PAT records (safe fields)
nestjs/src/personal-access-tokens/dto/created-personal-access-token.dto.ts Add DTO that includes full token (shown once)
nestjs/src/personal-access-tokens/dto/create-personal-access-token.dto.ts Add DTO + validation for PAT creation payload
nestjs/src/models/user.ts Add isSystem flag to users
nestjs/src/models/personalAccessToken.ts Add PAT entity model
nestjs/src/models/auditLog.ts Add audit log entity model
nestjs/src/models/index.ts Register/export AuditLog + PersonalAccessToken models
nestjs/src/migrations/index.ts Register migration for PAT + audit log tables
nestjs/src/courses/stats/course-stats.controller.ts Fix async authorization check by awaiting it
nestjs/src/courses/mentors/mentors.controller.ts Enforce course match for mentor endpoints via RequiredRoles(..., true)
nestjs/src/courses/courses.service.ts Add findManagedByUser helper
nestjs/src/courses/courses.controller.ts Add /managed-by-me; harden copyCourse parsing + course match
nestjs/src/courses/course-tasks/course-tasks.controller.ts Enforce course match for createCourseTask
nestjs/src/courses/course-students/course-students.controller.ts Harden getStudentSummary (staff-or-self) + course-scoped gates for search/expel
nestjs/src/courses/course-schedule/course-schedule.controller.ts Enforce course match for schedule copy
nestjs/src/cloud-api/cloud-api.service.ts Add requestCertificate call wrapper
nestjs/src/certificates/dto/eligible-student.dto.ts Add DTO for eligible student row
nestjs/src/certificates/dto/eligible-students-preview.dto.ts Add DTO for preview result (count + list)
nestjs/src/certificates/dto/eligible-students-criteria.dto.ts Add criteria DTO + validation rules
nestjs/src/certificates/dto/certificate-issuance-request.dto.ts Add DTO for issuance request payload
nestjs/src/certificates/dto/bulk-issue-result.dto.ts Add DTO for bulk issuance result
nestjs/src/certificates/certificates.module.ts Wire CloudApiModule into certificates module
nestjs/src/certificates/certificates.controller.ts Add endpoints for preview + bulk certificate issuance
nestjs/src/certificates/certificate-requests.spec.ts Update tests to provide CloudApiService mock
nestjs/src/auth/strategies/api-token.strategy.ts Add bearer strategy for PAT-backed API tokens
nestjs/src/auth/default.guard.ts Add api-token to default auth strategies
nestjs/src/auth/auth.module.ts Register PAT module + API token strategy
nestjs/src/auth/auth.service.ts Block system users from GitHub sign-in
nestjs/src/auth/auth-user.model.ts Track apiTokenId on request user
nestjs/src/auth/deny-api-token.decorator.ts Add decorator to mark endpoints as PAT-disallowed
nestjs/src/auth/api-token-deny.guard.ts Add guard enforcing @DenyApiToken()
nestjs/src/auth/index.ts Export new auth helpers
nestjs/src/auth/role.guard.ts Harden requireCourseMatch to deny when no courseId resolves
nestjs/src/auth/role.guard.spec.ts Update tests for new deny behavior
nestjs/src/audit-log/dto/audit-log-entry.dto.ts Add DTO for audit log entries
nestjs/src/audit-log/dto/audit-log-list.dto.ts Add DTO for paginated audit log list
nestjs/src/audit-log/audit-log.controller.ts Add admin-only audit log endpoint (API-token denied)
nestjs/src/audit-log/audit-log.module.ts Add module + global interceptor registration
nestjs/src/app.module.ts Wire PAT + audit log modules into app
nestjs/package.json Add passport-http-bearer deps/types
mcp/vitest.config.mts Add workspace vitest config + 100% coverage gate
mcp/tsconfig.json Add MCP workspace TS config
mcp/tsconfig.build.json Build TS config excluding tests/helpers
mcp/package.json Add publishable MCP package metadata + scripts
mcp/Dockerfile Add container image for streamable HTTP server
mcp/.env.example Document MCP env vars (base URL, PAT, allow-lists)
mcp/src/types.ts Define tool/role/toolset types + tool binding contracts
mcp/src/test-utils.ts Add tool test harness (recorded API calls)
mcp/src/format.ts Add JSON output formatter with truncation
mcp/src/format.spec.ts Test JSON formatting + truncation
mcp/src/redact.ts Add recursive output redaction (secrets + contacts)
mcp/src/redact.spec.ts Test redaction helpers
mcp/src/config.ts Add stdio env config reader + toolset parsing
mcp/src/config.spec.ts Test stdio config parsing + validation
mcp/src/http-main.ts Add streamable HTTP entrypoint + allow-list wiring
mcp/src/http-server.ts Add HTTP listener with CORS + /health + body limits
mcp/src/server.ts Add stdio entrypoint
mcp/src/registry.spec.ts Add registry integrity tests (naming/toolset/annotations)
mcp/src/tools/update-student-status.ts Add tool: update student status
mcp/src/tools/update-student-status.spec.ts Tests for update student status tool
mcp/src/tools/update-course-task.ts Add tool: update course task fields
mcp/src/tools/update-course-event.ts Add tool: update course event fields
mcp/src/tools/update-course-event.spec.ts Tests for update course event tool
mcp/src/tools/submit-task-solution.ts Add tool: submit task solution URL
mcp/src/tools/submit-task-solution.spec.ts Tests for submit task solution tool
mcp/src/tools/submit-task-score.ts Add tool: submit task score
mcp/src/tools/submit-task-score.spec.ts Tests for submit task score tool
mcp/src/tools/submit-multiple-scores.ts Add tool: submit multiple scores
mcp/src/tools/submit-multiple-scores.spec.ts Tests for submit multiple scores tool
mcp/src/tools/submit-interview-feedback.ts Add tool: submit stage interview feedback
mcp/src/tools/submit-interview-feedback.spec.ts Tests for submit interview feedback tool
mcp/src/tools/submit-cross-check-review.ts Add tool: submit cross-check review
mcp/src/tools/submit-cross-check-review.spec.ts Tests for submit cross-check review tool
mcp/src/tools/search-users.ts Add tool: user search (admin-only system visibility)
mcp/src/tools/search-users.spec.ts Tests for search users tool
mcp/src/tools/register-to-interview.ts Add tool: register to interview
mcp/src/tools/register-to-interview.spec.ts Tests for register to interview tool
mcp/src/tools/preview-eligible-students.ts Add tool: preview certificate eligibility
mcp/src/tools/preview-eligible-students.spec.ts Tests for preview eligible students tool
mcp/src/tools/issue-certificates-bulk.ts Add tool: bulk certificate issuance
mcp/src/tools/issue-certificates-bulk.spec.ts Tests for bulk issuance tool
mcp/src/tools/issue-certificate.ts Add tool: single certificate issuance
mcp/src/tools/issue-certificate.spec.ts Tests for single issuance tool
mcp/src/tools/list-my-students.ts Add tool: list mentor’s students
mcp/src/tools/list-my-students.spec.ts Tests for list my students tool
mcp/src/tools/list-my-courses.ts Add tool: list courses for PAT user
mcp/src/tools/list-mentor-registry.ts Add tool: list mentor registry
mcp/src/tools/list-mentor-registry.spec.ts Tests for list mentor registry tool
mcp/src/tools/list-course-tasks.ts Add tool: list course tasks
mcp/src/tools/list-course-tasks.spec.ts Tests for list course tasks tool
mcp/src/tools/list-course-students-details.ts Add tool: list course students details (redacted)
mcp/src/tools/list-course-students-details.spec.ts Tests for list course students details tool
mcp/src/tools/list-course-mentors-details.ts Add tool: list course mentors details (redacted)
mcp/src/tools/list-course-mentors-details.spec.ts Tests for list course mentors details tool
mcp/src/tools/list-course-events.ts Add tool: list course events
mcp/src/tools/list-course-events.spec.ts Tests for list course events tool
mcp/src/tools/get-student-summary.ts Add tool: get student summary (contact redaction)
mcp/src/tools/get-student-summary.spec.ts Tests for get student summary tool
mcp/src/tools/get-my-score.ts Add tool: get own student score
mcp/src/tools/get-my-score.spec.ts Tests for get my score tool
mcp/src/tools/get-my-profile.ts Add tool: get own profile
mcp/src/tools/get-my-profile.spec.ts Tests for get my profile tool
mcp/src/tools/get-my-mentor-interviews.ts Add tool: get mentor interviews (me)
mcp/src/tools/get-my-mentor-interviews.spec.ts Tests for get my mentor interviews tool
mcp/src/tools/get-my-interviews.ts Add tool: get student interviews (me)
mcp/src/tools/get-my-interviews.spec.ts Tests for get my interviews tool
mcp/src/tools/get-my-interview-students.ts Add tool: list stage interview students assigned to me
mcp/src/tools/get-my-interview-students.spec.ts Tests for get my interview students tool
mcp/src/tools/get-my-cross-check-review-stats.ts Add tool: cross-check remaining review stats
mcp/src/tools/get-my-cross-check-review-stats.spec.ts Tests for cross-check review stats tool
mcp/src/tools/get-my-cross-check-result.ts Add tool: cross-check result for me
mcp/src/tools/get-my-cross-check-result.spec.ts Tests for cross-check result tool
mcp/src/tools/get-my-cross-check-feedbacks.ts Add tool: cross-check feedbacks for my solution
mcp/src/tools/get-my-cross-check-feedbacks.spec.ts Tests for cross-check feedbacks tool
mcp/src/tools/get-my-cross-check-assignments.ts Add tool: cross-check assignments for me
mcp/src/tools/get-my-cross-check-assignments.spec.ts Tests for cross-check assignments tool
mcp/src/tools/get-mentor-reviews.ts Add tool: mentor review activity listing
mcp/src/tools/get-mentor-reviews.spec.ts Tests for get mentor reviews tool
mcp/src/tools/get-mentor-dashboard.ts Add tool: mentor dashboard
mcp/src/tools/get-mentor-dashboard.spec.ts Tests for mentor dashboard tool
mcp/src/tools/get-interview-feedback.ts Add tool: get stage interview feedback form
mcp/src/tools/get-interview-feedback.spec.ts Tests for get interview feedback tool
mcp/src/tools/get-course-stats.ts Add tool: course stats
mcp/src/tools/get-course-stats.spec.ts Tests for get course stats tool
mcp/src/tools/get-course-schedule.ts Add tool: course schedule (optional upcoming-only)
mcp/src/tools/get-course-schedule.spec.ts Tests for get course schedule tool
mcp/src/tools/get-course-leaderboard.ts Add tool: course leaderboard
mcp/src/tools/get-course-leaderboard.spec.ts Tests for leaderboard tool
mcp/src/tools/get-course-interviews.ts Add tool: list course interviews
mcp/src/tools/get-course-interviews.spec.ts Tests for course interviews tool
mcp/src/tools/expel-students.ts Add tool: bulk expel students
mcp/src/tools/expel-students.spec.ts Tests for expel students tool
mcp/src/tools/distribute-interview-pairs.ts Add tool: auto-distribute interview pairs
mcp/src/tools/distribute-interview-pairs.spec.ts Tests for distribute interview pairs tool
mcp/src/tools/delete-course-task.ts Add tool: delete course task
mcp/src/tools/delete-course-task.spec.ts Tests for delete course task tool
mcp/src/tools/delete-course-event.ts Add tool: delete course event
mcp/src/tools/delete-course-event.spec.ts Tests for delete course event tool
mcp/src/tools/create-stage-interviews.ts Add tool: create stage interview pairs
mcp/src/tools/create-stage-interviews.spec.ts Tests for create stage interviews tool
mcp/src/tools/create-interview-result.ts Add tool: create interview result
mcp/src/tools/create-interview-result.spec.ts Tests for create interview result tool
mcp/src/tools/create-cross-check-distribution.ts Add tool: start cross-check distribution
mcp/src/tools/create-cross-check-distribution.spec.ts Tests for cross-check distribution tool
mcp/src/tools/create-course-task.ts Add tool: create course task
mcp/src/tools/create-course-task.spec.ts Tests for create course task tool
mcp/src/tools/create-course-event.ts Add tool: create course event
mcp/src/tools/create-course-event.spec.ts Tests for create course event tool
mcp/src/tools/course-task-fields.ts Shared schema for course-task create/update tools
mcp/src/tools/course-event-fields.ts Shared schema for course-event create/update tools
mcp/src/tools/complete-cross-check.ts Add tool: complete cross-check task
mcp/src/tools/complete-cross-check.spec.ts Tests for complete cross-check tool
mcp/src/tools/certificate-criteria.ts Shared schema for certificate criteria tools
mcp/src/tools/certificate-criteria.spec.ts Tests for certificate criteria schema
mcp/src/tools/approve-mentor.ts Add tool: approve mentor with course scoping
mcp/src/tools/approve-mentor.spec.ts Tests for approve mentor tool
mcp/src/tools/grant-course-roles.spec.ts Tests for course role grant tool behavior
docker-compose.yml Add mcp service and nginx dependency
client/src/shared/components/Sider/data/menuItems.tsx Add admin menu items: system users, tokens, audit log
client/src/shared/components/Header.tsx Add profile menu link to API tokens
client/src/pages/profile/api-tokens.tsx Add profile page route for PAT management
client/src/pages/admin/user-tokens.tsx Add admin page route for managing user tokens
client/src/pages/admin/system-users.tsx Add admin page route for managing system users
client/src/pages/admin/audit-log.tsx Add admin page route for audit log
client/src/modules/SystemUsers/pages/SystemUsersPage/index.ts Export SystemUsers page
client/src/modules/SystemUsers/index.ts Export SystemUsers module surface
client/src/modules/AuditLog/pages/AuditLogPage/index.ts Export AuditLog page
client/src/modules/AuditLog/index.ts Export AuditLog module surface
client/src/modules/ApiTokens/pages/ApiTokensPage/index.ts Export profile tokens page
client/src/modules/ApiTokens/pages/UserTokensAdminPage/index.ts Export admin tokens page
client/src/modules/ApiTokens/index.ts Export ApiTokens module surface
.github/workflows/deploy.yaml Add build_mcp job; include in deploy dependencies

Comment thread setup/nginx/nginx.conf
Comment thread mcp/src/http-server.ts Outdated
Comment thread nestjs/src/auth/strategies/api-token.strategy.ts Outdated
Comment thread nestjs/src/users/dto/system-user.dto.ts Outdated
@AlreadyBored
AlreadyBored force-pushed the alreadybored/initial-mcp-support branch from d3697c2 to dff12a7 Compare July 20, 2026 09:56
@AlreadyBored AlreadyBored changed the title Draft: Initial mcp support Initial mcp support Jul 20, 2026
AlreadyBored and others added 4 commits July 27, 2026 20:55
…l-mcp-support

# Conflicts:
#	nestjs/src/courses/course-students/course-students-controller-reads.spec.ts
#	package-lock.json
…ints

The UI issue path (#3100) gained taskCriteria — a minimum score per task —
but the /eligible and /bulk endpoints still expressed one shared minScore
for every task, so the same course could yield two different eligible sets
depending on which path issued the certificates.

Both criteria shapes now normalize to per-task thresholds before the query,
with taskCriteria taking precedence and the flat form expanded by giving
every listed task the shared bar. Existing callers are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants