Initial mcp support - #3000
Open
AlreadyBored wants to merge 29 commits into
Open
Conversation
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>
Contributor
There was a problem hiding this comment.
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
/mcpproxy + 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 |
AlreadyBored
force-pushed
the
alreadybored/initial-mcp-support
branch
from
July 20, 2026 09:56
d3697c2 to
dff12a7
Compare
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
mcp/package, deployed as a compose container behind nginx at/mcp; also runnable over stdio for local clients.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.common/student/mentor/course-management/course-admin/users). Tool visibility is role-filtered and can be narrowed further viaRSAPP_TOOLSETS. Write/destructive tools are described to require explicit user confirmation.Backend
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
RoleGuarddefaulting torequireCourseMatch=false, with tools forwardingcourseId/githubIdverbatim.expel_students,createCourseTask,copyFrom(schedule),copyCourse, and the mentordashboard/students-count/random-taskroutes now require the role in the target course (requireCourseMatch: true) — a role in one course no longer grants the action in another.getStudentSummarywasDefaultGuard-only (any authenticated user could read any student's score + mentor contacts). NowCourseGuard+ an in-body staff-or-self check — the student dashboard still reads its own summary.getCourseStats: thecanAccessCoursecheck was dead code (async, never awaited) — now awaited.searchCourseStudents: added a course-scoped role gate.RoleGuardhardened to deny instead of silently falling back to the any-course check when a course match is required but nocourseIdresolves.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:includeSystemenforced as admin-only.approve_mentor: preselected courses scoped to ones the caller manages/supervises.Transport / output / input
RSAPP_ALLOWED_HOSTS/RSAPP_ALLOWED_ORIGINS; CORS allow-list replaces the wildcard*.get_student_summary; backend 5xx bodies collapse to a generic message.grant_course_rolesrefuses a call that names no role (which would revoke everything).Docs: documented the nginx
/mcprate-limit requirement (the container bypasses the nginx/api/v2layer) and PAT rotation.Tests: MCP
test:ci— 257 tests @ 100% coverage; NestJS — 2070 tests @ ~96% coverage. ESLint +oxfmtclean.Known follow-ups (not in this PR):
searchMentorsrole enforcement, registrycancel/commentcourse-scoping, and the client-sidelimitin the list-details tools.Self-Check: