Vetrix Docs

OAuth2 & MCP

Reference for the OAuth2 authorization-code flow — discovery, authorization, token exchange, and dynamic client registration — and for the Model Context Protocol (MCP) endpoint that exposes read-only repository tools to AI assistants.

Resource overview

This page covers two related surfaces.

OAuth2 authorization server. A third-party application obtains an access token on a user's behalf through the authorization-code flow with PKCE. The flow spans four endpoint groups:

  • Discovery metadata at /.well-known/oauth-authorization-server (RFC 8414) and /.well-known/oauth-protected-resource/api/v1/mcp/{owner}/{repo} (RFC 9728), which let a client bootstrap with no out-of-band configuration.
  • The authorization endpoint /api/v1/oauth2/authorize, which backs the browser consent screen.
  • The token endpoint /api/v1/oauth2/token, plus token revoke (RFC 7009) and introspect (RFC 7662).
  • Dynamic client registration at /api/v1/oauth2/register (RFC 7591).

MCP endpoint. /api/v1/mcp/{owner}/{repo} is a per-repository JSON-RPC endpoint that exposes a fixed set of read-only tools (repository browsing, issue reading, and search). /api/v1/repos/{owner}/{repo}/mcp/info returns the descriptor a UI uses to display the connection URL and toolset.

The OAuth2 endpoints and the MCP audience are advertised as absolute URLs built from the instance's configured external base URL, so the discovery documents always name the hosts that deployment actually serves. The token, revocation, introspection, and registration endpoints, and the MCP audience, resolve to the REST API host; the authorization_endpoint resolves to the browser-facing consent page on the web UI host (see the host table in conventions.md). On a single-origin deployment every URL shares one host.

Interactive registration and management of OAuth2 applications — creating an app, rotating its secret, and reviewing or revoking the applications a user has authorized — are done through the web console; dynamic client registration below is the programmatic equivalent for clients that self-register.

Auth & scopes

See conventions.md for the accepted credential types, the OAuth2 access-token format (vetrix_oat_ / vetrix_ort_), the full scope catalog, and how scopes are enforced. Scope enforcement is additive: an OAuth2 caller must hold both the granted scope and the underlying ACL permission.

The flow endpoints are part of the authentication bootstrap rather than scope-gated resources:

  • Discovery documents are public.
  • /authorize requires a signed-in user session (the consent screen acts as that user).
  • /token, /revoke, and /introspect authenticate the client, not a scope.
  • /register (dynamic client registration) is gated by an instance admin setting and is disabled by default.

The MCP endpoint is the scope-gated resource on this page:

Operation OAuth2 scope PAT scope
Connect to the MCP endpoint mcp:read mcp:read

A caller must additionally have read access to the target repository; tools are gated against that access per call (see The MCP endpoint).

Endpoints

Method Path Summary
GET /.well-known/oauth-authorization-server Authorization-server metadata (RFC 8414)
GET /.well-known/oauth-protected-resource/api/v1/mcp/{owner}/{repo} Protected-resource metadata (RFC 9728)
GET /api/v1/oauth2/authorize Fetch consent metadata for an authorization request
POST /api/v1/oauth2/authorize Submit the consent decision and issue a code
POST /api/v1/oauth2/token Exchange a code or refresh token for tokens
POST /api/v1/oauth2/revoke Revoke an access or refresh token
POST /api/v1/oauth2/introspect Introspect a token
POST /api/v1/oauth2/register Register a client dynamically
POST /api/v1/mcp/{owner}/{repo} MCP JSON-RPC wire endpoint
GET /api/v1/repos/{owner}/{repo}/mcp/info Read a repository's MCP descriptor
PUT /api/v1/repos/{owner}/{repo}/mcp/settings Enable or disable MCP for a repository

GET /.well-known/oauth-authorization-server

Authorization-server metadata (RFC 8414). A client fetches this first to discover the authorization, token, and registration endpoints and the server's capabilities. Public.

Response

{
  "issuer": "https://api.gitvetrix.com",
  "authorization_endpoint": "https://www.gitvetrix.com/oauth2/authorize",
  "token_endpoint": "https://api.gitvetrix.com/api/v1/oauth2/token",
  "revocation_endpoint": "https://api.gitvetrix.com/api/v1/oauth2/revoke",
  "introspection_endpoint": "https://api.gitvetrix.com/api/v1/oauth2/introspect",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": [
    "read:user", "read:repo", "write:repo", "admin:repo",
    "read:issue", "write:issue", "read:pipeline", "write:pipeline",
    "read:package", "write:package", "read:audit",
    "openid", "profile", "email", "mcp:read"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_basic",
    "client_secret_post",
    "none"
  ]
}
Field Description
authorization_endpoint Browser consent page on the web UI host.
token_endpoint / revocation_endpoint / introspection_endpoint API-host endpoints below.
code_challenge_methods_supported Only S256; PKCE is required (see Token).
token_endpoint_auth_methods_supported none is for public clients (PKCE only); client_secret_basic / client_secret_post are for confidential clients.
registration_endpoint Present only when dynamic client registration is enabled; otherwise omitted.

The response carries Cache-Control: no-store because the document varies with the registration setting.

Status codes

Status When
200 OK The authorization server is enabled.
503 Service Unavailable The authorization server is disabled instance-wide.

Example

curl https://api.gitvetrix.com/.well-known/oauth-authorization-server

GET /.well-known/oauth-protected-resource/api/v1/mcp/{owner}/{repo}

Protected-resource metadata (RFC 9728) for one repository's MCP endpoint. A client that receives a 401 from the MCP endpoint follows the WWW-Authenticate challenge to this document to learn which authorization server protects the resource. Public.

Path parameters

Name Type Description
{owner} string Repository owner (user or organization).
{repo} string Repository name.

Response

{
  "resource": "https://api.gitvetrix.com/api/v1/mcp/{owner}/{repo}",
  "authorization_servers": ["https://api.gitvetrix.com"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["mcp:read"]
}

resource is the canonical MCP audience an OAuth2 token must target (see the resource parameter on /authorize). authorization_servers names where to fetch the RFC 8414 metadata above.

Status codes

Status When
200 OK The authorization server is enabled.
503 Service Unavailable The authorization server is disabled instance-wide.

GET /api/v1/oauth2/authorize

Returns the consent metadata the browser consent screen renders before a user approves an application. Requires a signed-in user session.

Query parameters

Name Type Description
client_id string The application's client ID. Required.
redirect_uri string Must exactly match a URI registered for the client. Required.
response_type string Must be code. Required.
scope string Space-separated scopes being requested.
state string Opaque value echoed back on the callback; use it for CSRF defense.
code_challenge string PKCE challenge: the base64url-encoded SHA-256 of the verifier. Required.
code_challenge_method string Must be S256. Required.
resource string Optional RFC 8707 audience. When present it must equal the MCP resource from the protected-resource metadata; any other value is rejected with invalid_target.

Response

{
  "decision": "consent_required",
  "app_name": "Example Assistant",
  "description": "...",
  "homepage_url": "https://example.com",
  "logo_url": "https://example.com/logo.png",
  "scopes_requested": ["read:repo", "mcp:read"],
  "grant_exists": false,
  "remembered_scopes": []
}

When the user has already granted a superset of the requested scopes, the response instead carries an auto_approve_redirect_url the client navigates to directly — no second prompt.

Status codes

Status When
200 OK Consent metadata returned (or an auto-approve redirect).
400 Bad Request response_type is not code, or PKCE S256 is missing.
401 Unauthorized No signed-in user session.
302 Found A redirect back to the registered redirect_uri carrying an error parameter (RFC 6749 §4.1.2.1) — for example invalid_scope when a public client requests an admin-only scope, or invalid_target for a bad resource. Becomes a 400 Bad Request carrying the same error code when no usable redirect_uri is available.

POST /api/v1/oauth2/authorize

Submits the user's consent decision and, on approval, issues a short-lived authorization code. The body is form-encoded. Requires a signed-in user session and the same query parameters as the GET form.

Request body

Field Description
decision cancel to deny; any other value approves.
remember true to persist the grant for silent re-approval; false to forget any prior grant.

Response

On approval, and on cancel, the response is a JSON object naming the URL to navigate to. The approval URL carries the authorization code and the echoed state; the cancel URL carries error=access_denied.

{ "redirect_url": "https://example.com/callback?code=<code>&state=<state>" }

The authorization code is single-use and expires ten minutes after issuance.

POST /api/v1/oauth2/token

Exchanges an authorization code (or a refresh token) for an access token. The body is application/x-www-form-urlencoded. A confidential client authenticates with HTTP Basic (client_secret_basic) or by sending client_id and client_secret in the body (client_secret_post); a public client sends client_id and relies on PKCE.

Request body — authorization code

Field Description
grant_type authorization_code. Required.
code The authorization code from /authorize. Required.
redirect_uri Must match the redirect_uri used at /authorize. Required.
client_id The application's client ID (or supplied via HTTP Basic).
code_verifier The PKCE verifier whose SHA-256 matches the code_challenge. Required.

Request body — refresh token

Field Description
grant_type refresh_token. Required.
refresh_token A vetrix_ort_ refresh token. Required.
client_id The application's client ID (or supplied via HTTP Basic).

Response

{
  "access_token": "vetrix_oat_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:repo mcp:read",
  "refresh_token": "vetrix_ort_..."
}
Field Description
access_token The OAuth2 access token, prefixed vetrix_oat_.
expires_in Seconds until the access token expires (one hour by default).
scope Space-separated scopes actually granted.
refresh_token A new vetrix_ort_ refresh token; present when refresh is configured.
id_token A signed OpenID Connect ID token; present only when the openid scope was granted.

The response carries Cache-Control: no-store.

Refresh-token rotation. Each refresh exchange invalidates the presented refresh token and returns a new one in the same rotation family. Replaying a refresh token that has already been rotated is treated as a compromise: the entire family is revoked and the request fails with 400 Bad Request, invalid_grant, "refresh token reuse detected". Always store and use only the most recently returned refresh token.

Status codes

Status When
200 OK Tokens issued.
400 Bad Request Unsupported grant_type, PKCE verification failed, redirect_uri mismatch, or refresh-token reuse / invalidity.
401 Unauthorized Client authentication failed.

Example

curl -X POST https://api.gitvetrix.com/api/v1/oauth2/token \
  -d grant_type=authorization_code \
  -d code=<code> \
  -d redirect_uri=https://example.com/callback \
  -d client_id=<client_id> \
  -d code_verifier=<verifier>

POST /api/v1/oauth2/revoke

Revokes an access or refresh token (RFC 7009). The body is form-encoded with a token field and an optional token_type_hint. The client authenticates as on the token endpoint. The response is 200 OK whether or not the token existed, per the specification.

POST /api/v1/oauth2/introspect

Reports whether a token is currently active (RFC 7662). The body is form-encoded with a token field. Confidential-client authentication is required; public clients are refused. The response is the RFC 7662 introspection object with an active boolean.

POST /api/v1/oauth2/register

Registers an OAuth2 client dynamically (RFC 7591). Disabled by default; an instance admin enables it. While it is disabled an anonymous caller receives 404 Not Found (the endpoint is indistinguishable from one that is not mounted) and an authenticated admin receives 403 Forbidden with access_denied.

Two caller classes are served when registration is enabled:

  • An authenticated instance admin (holding the admin:oauth2 scope) may pre-register confidential clients.
  • An anonymous caller (no bearer) may self-register a zero-configuration MCP connector. Anonymous registrations are restricted to the single mcp:read scope and are subject to a per-IP throttle and an instance-wide client cap.

Request body

{
  "redirect_uris": ["https://example.com/callback"],
  "token_endpoint_auth_method": "none",
  "client_name": "Example Assistant",
  "scope": "mcp:read"
}
Field Description
redirect_uris One or more callback URIs. Each must be https, or http on a loopback address (RFC 8252). Required.
token_endpoint_auth_method none (or omitted) registers a public client; client_secret_basic / client_secret_post registers a confidential client and mints a secret.
client_name Human-readable application name.
scope Requested scopes. Anonymous registrations may request only mcp:read.

Response

201 Created with the client-information response. client_secret and client_secret_expires_at are present only for a confidential client.

{
  "client_id": "...",
  "client_id_issued_at": 1700000000,
  "client_secret": "...",
  "client_secret_expires_at": null,
  "redirect_uris": ["https://example.com/callback"],
  "token_endpoint_auth_method": "none",
  "client_name": "Example Assistant",
  "scope": "mcp:read"
}

Status codes

Status When
201 Created Client registered.
400 Bad Request invalid_client_metadata, invalid_redirect_uri, or invalid_scope.
403 Forbidden Registration is disabled (authenticated admin), or the caller lacks admin:oauth2.
404 Not Found Registration is disabled (anonymous caller).
503 Service Unavailable Registration is enabled but its backing store is not wired.

The MCP endpoint

POST /api/v1/mcp/{owner}/{repo} is a JSON-RPC endpoint spoken over Streamable HTTP (initialize, then tools/list and tools/call). It exposes read-only tools scoped to a single repository.

Authentication. Send either an OAuth2 access token (vetrix_oat_) carrying the mcp:read scope, or a personal access token with the mcp:read scope, as a bearer credential. An OAuth2 token must also have been minted for this endpoint's audience (the resource from the protected-resource metadata).

Gate order. The endpoint applies these checks in order, and each rejection is a protocol-level error rather than a tool result:

Status Code When
401 Unauthorized unauthorized No bearer token. The response carries a WWW-Authenticate: Bearer challenge whose resource_metadata points at the protected-resource document.
503 Service Unavailable MCP is disabled instance-wide.
403 Forbidden insufficient_scope The token lacks mcp:read. The challenge again points at the protected-resource document.
403 Forbidden invalid_audience An OAuth2 token's audience does not match this endpoint.
404 Not Found not_found The repository does not exist, or the caller may not read it (private repositories return 404, never 403 — see errors.md).
503 Service Unavailable mcp_disabled_for_repo MCP is turned off for this specific repository.

Tools. tools/list returns a fixed set of read-only tools. Every tool runs with the caller's own read access — a tool only ever returns repositories, issues, comments, and content the caller is already permitted to see — and none of them mutate state.

Repository tools:

Tool Returns
repo_info Repository metadata: name, default branch, description, visibility, size.
list_branches Branches with default/protected flags (cursor-paginated).
list_tags Tags with the first line of the annotation (cursor-paginated).
list_tree Files and directories at a (ref, path) with last-commit metadata.
read_file File text at a (ref, path), optionally a line range.
list_commits Commits on a ref, optionally filtered by path (cursor-paginated).
show_commit A commit's envelope and per-file add/delete counts (no patch).
get_file_at_two_refs_diff The unified diff of one file between two refs.

Issue tools:

Tool Returns
list_issues Repository issues, optionally filtered by state and label (cursor-paginated).
read_issue One issue by number (comments excluded).
list_comments Comments on an issue, with restricted comments filtered out (cursor-paginated).
search_issues Full-text issue search ranked by relevance.

Search tools:

Tool Returns
search_code Code search across the repositories the caller can read.
find_symbol Symbol definitions by name from the symbol index.

Example

curl -X POST https://api.gitvetrix.com/api/v1/mcp/{owner}/{repo} \
  -H "Authorization: Bearer vetrix_oat_..." \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

GET /api/v1/repos/{owner}/{repo}/mcp/info

Returns the MCP descriptor for a repository, used to display the connection URL and toolset. Session-authenticated; gated by repository read access (a caller who cannot read the repository receives 404).

Response

{
  "server_url": "https://api.gitvetrix.com/api/v1/mcp/{owner}/{repo}",
  "capabilities": ["repo", "issues", "search"],
  "enabled": true,
  "instance_enabled": true
}
Field Description
server_url The wire endpoint a client connects to.
capabilities The toolset families the server exposes.
enabled The per-repository MCP toggle.
instance_enabled The instance-wide MCP kill switch. The endpoint is usable only when both are true.

PUT /api/v1/repos/{owner}/{repo}/mcp/settings

Enables or disables MCP for one repository. Requires repository-admin permission; a caller who cannot administer the repository receives 404 (the same opaque response a non-existent repository returns). The body is { "enabled": <bool> }, and the response is the same descriptor as /mcp/info.

Errors

These endpoints use the shared error envelope and status-code conventions in errors.md, including the 404-not-403 rule for private repositories. The OAuth2 endpoints additionally return the flow-specific error codes defined by their specifications, as a JSON error (with an optional error_description):

Code Where Meaning
invalid_request authorize, token A required parameter is missing or malformed (including a missing PKCE S256 challenge).
invalid_client authorize, token Unknown, suspended, or unauthenticated client.
invalid_grant token Bad code, redirect_uri mismatch, failed PKCE, or refresh-token reuse / invalidity.
invalid_scope authorize, register An unknown scope, an admin-only scope for a public client, or a non-mcp:read scope on an anonymous registration.
invalid_target authorize The resource parameter is not this instance's MCP audience.
unsupported_response_type authorize response_type is not code.
unsupported_grant_type token grant_type is neither authorization_code nor refresh_token.
access_denied authorize, register The user denied consent, or registration is disabled.
invalid_client_metadata / invalid_redirect_uri register Malformed registration request or an unacceptable redirect URI.
temporarily_unavailable discovery The authorization server is disabled.
server_error any An unexpected server-side failure.

The MCP endpoint reports its gate rejections with the codes listed in The MCP endpoint.

Rate limits

These endpoints are metered under the standard scopes described in rate-limits.md: the discovery and GET /authorize endpoints count as api.read, and the POST OAuth2 endpoints count as api.write. MCP wire calls are metered under the dedicated mcp.tool_call scope so AI-assistant traffic cannot starve ordinary REST traffic.