Agent Access for Developers
Agent access lets a user give a coding agent a personal access token that calls a limited part of the REST API on their behalf. This page is for developers who add or change what a token can reach. The setup for administrators is in Agent Access, and what it means for instructors is in Working with Coding Agents.
How a request is checked
A request with a bearer token that starts with art_pat_ is authenticated by PersonalAccessTokenFilter as the token's owner,
unless the owner is an administrator. AllowedScopesInterceptor then lets it reach a handler only if the handler carries
@AllowedScopes and the token holds every listed scope. The ordinary role annotation of the handler still applies, so a token can do
at most what its owner can do in that course, and at most what its scopes allow.
Every endpoint is closed to tokens until you opt it in. That is the point: a new endpoint must not become reachable by an agent by accident.
Opt an endpoint in
Add @AllowedScopes(AgentScope.X) to the handler, together with an @EnforceAtLeast... annotation and an
@Operation with a summary and a description written for an agent, not for a developer: what it does, what it needs, what it refuses.
Three rules are enforced by AllowedScopesArchitectureTest:
- The handler has an
@EnforceAtLeast...annotation, because a scope narrows access and never replaces the role check. - The handler takes and returns DTOs. The agent specification lists only DTO schemas, so an endpoint that uses a domain object would drop out of it without an error.
- The committed agent specification lists the handler, with a summary. Regenerate it as described below.
Choose the scope by what the endpoint does. There is deliberately no scope for grades, exams, student data, deleting or publishing; do not add one without a separate threat review.
Keep content a draft
Exercise and lecture-unit endpoints with the draft-date contract must keep token requests within that contract. AgentDraftRules
holds these rules, which apply only to token requests:
requireDraftDatesForCreaterequires a release date in the future and, if configured, a future start date. Exercise validation separately requires the start date to follow the release date.requireStillDraftlets a token change only content that is not visible yet, and only in a way that keeps it so.rejectExamExercisekeeps exam exercises out of reach.
A programming exercise additionally has no student participations, which ProgrammingExerciseDraftGuardService checks. Call the rule
before the request is applied to the stored object, and add a test that a token cannot publish through the endpoint. Announcements,
competencies and lecture metadata are immediately visible. The MCP announcement and lecture-creation tools therefore require
confirmation that the instructor approved the exact text; AgentDraftRules does not hide a newly created lecture.
Regenerate the agent specification
The agent API specification is generated from the annotations and committed to documentation/static/agent/openapi.yaml, so that it can be
published. After you opt an endpoint in or change its documentation, run:
./gradlew generateOpenApiDocs -PopenApiGroup=agent pruneOpenApiSpec -x webapp
The run starts a throwaway server on port 8080 and its SSH server on 7921. If a development server uses them, move the throwaway
server with -PopenApiServerPort=<port> and -PopenApiSshPort=<port>. The application specification, which feeds the generated
client, is produced by the same command without -PopenApiGroup=agent and covers only the packages listed in gradle/openapi.gradle.
Tools of the MCP endpoint
/api/core/agent/mcp serves the Model Context Protocol. Its tools, in AgentToolService, are tasks an instructor would name, not wrappers
of endpoints, and each one calls the REST API of the same server through AgentApi with the token of the request. Opt the endpoint in
first; a tool never grants anything the endpoint does not. A new tool needs a scope that the token must hold to see it, a description that
tells the model when to use it and what to confirm first, and typed arguments: a record in AgentToolArguments that the arguments are read
as, and a JsonSchemaDTO that describes them to the agent. A body that the tool sends on is the DTO of the endpoint it calls, never a
JsonNode; the whole endpoint, protocol messages included, is written with records, as the
REST API guideline asks.