Skip to main content

MCP OAuth (connector authorization)

First-time workspace setup

Operators connecting Claude.ai or ChatGPT MCP connectors do not call these routes manually — the connector platform runs the flow. Administrators must enable at least one tool domain on Account Settings → AI / MCP before authorization succeeds. Pair operator context with Landlord MCP — Connector OAuth and Management session authentication (browser JWT contrast). Workflow pairing: Setup sequence after go-live (hub: API Reference — Setup sequence after go-live).

Vivin's Core API hosts an OAuth 2.1 Authorization Server for hosted MCP connectors (for example Claude.ai and ChatGPT). Connectors discover endpoints, register dynamically, run PKCE authorization code + refresh-token grants, and receive a management JWT the Landlord MCP server already accepts at /mcp.

This page documents the HTTP contract on the API host. For operator setup, consent behaviour, and when to use API keys instead, see Landlord MCP — Connector OAuth.

Not partner integration auth

These routes are not the partner Authentication Bearer keys used on /{platform}-integration prefixes. They are also not POST /auth/login — see Management session authentication.

Who uses these routes

CallerTypical use
Claude.ai / ChatGPT MCP connectorsFull OAuth flow — discovery → register → authorize → token
Native/desktop MCP clientsSame flow with loopback redirect_uri values (http://localhost / http://127.0.0.1, RFC 8252)
IDE configs (Cursor, Claude Desktop)Usually paste an API key Bearer token instead — no OAuth
Custom scriptsPrefer Management session login or Landlord MCP with an API key

All routes are public (no Authorization header on discovery/register/authorize/token) but rate-limited per client IP. The consenting landlord is identified from a Vivin refresh cookie or email/password on the consent form — never from unauthenticated client-supplied account ids.

Discovery

RFC 8414 metadata is exposed at either well-known path on the API host:

GET /.well-known/oauth-authorization-server HTTP/1.1
Host: api.vivin.app
GET /.well-known/openid-configuration HTTP/1.1
Host: api.vivin.app

Both return the same JSON (CORS *, cache max-age=3600). Example shape:

{
"issuer": "https://api.vivin.app",
"authorization_endpoint": "https://api.vivin.app/oauth/authorize",
"token_endpoint": "https://api.vivin.app/oauth/token",
"registration_endpoint": "https://api.vivin.app/oauth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
"scopes_supported": [
"bookings",
"listings",
"properties",
"tenants",
"maintenance",
"payments",
"bills",
"owners",
"analytics"
]
}

issuer and endpoint URLs follow the request host (including staging hosts such as dapi.vivin.app).

Dynamic client registration

RFC 7591 registration for trusted connector callbacks:

POST /oauth/register HTTP/1.1
Host: api.vivin.app
Content-Type: application/json

{
"client_name": "Claude MCP",
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none"
}

Response (201) — registered client metadata (optional client_secret when auth method requires it):

{
"client_id": "…",
"client_id_issued_at": 1710000000,
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none",
"client_name": "Claude MCP"
}

Registration rejects redirect URIs outside the trusted policy.

Authorization code flow

sequenceDiagram
participant Connector as MCP connector
participant API as Core API (OAuth)
participant Landlord as Landlord browser
participant MCP as Landlord MCP /mcp
Connector->>API: GET /.well-known/oauth-authorization-server
Connector->>API: POST /oauth/register
Connector->>Landlord: Redirect GET /oauth/authorize (PKCE challenge)
Landlord->>API: Consent (session cookie or login form)
API-->>Connector: Redirect with ?code=
Connector->>API: POST /oauth/token (code + code_verifier)
API-->>Connector: access_token (JWT) + refresh_token
Connector->>MCP: Bearer JWT → Mcp-Session-Id
MCP->>API: Tool calls with management JWT

Authorize

GET /oauth/authorize?response_type=code
&client_id=<client_id>
&redirect_uri=<registered_uri>
&code_challenge=<S256_challenge>
&code_challenge_method=S256
&state=<opaque>
&scope=bookings%20listings HTTP/1.1
Host: api.vivin.app
ParameterRequirement
response_typeMust be code
client_idRegistered client
redirect_uriExact match to a registered URI (loopback: port-agnostic per RFC 8252)
code_challenge / code_challenge_methodPKCE mandatory — only S256 accepted (plain rejected)
scopeOptional space-separated subset of supported scopes
stateRecommended opaque value echoed on redirect

Behaviour

  • If the landlord already has a valid Vivin refresh cookie, the HTML consent page shows their email and the tool domains they will grant.
  • Otherwise the consent form collects email/password, then shows the same allow/deny UI.
  • Allow redirects to redirect_uri with ?code=…&state=… (authorization code TTL 120 seconds, single-use).
  • Deny redirects with error=access_denied.
  • Invalid client_id or redirect_uri returns an inline HTML error page (no redirect — avoids open-redirect leaks).
  • Zero enabled MCP tool domains for the account returns access_denied — enable domains on AI / MCP → Tool permissions first.

OAuth consent — signed-in landlord with allowed MCP tool list (Authorize / Cancel)

Token

POST /oauth/token accepts application/x-www-form-urlencoded or JSON bodies.

Authorization code grant

POST /oauth/token HTTP/1.1
Host: api.vivin.app
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<authorization_code>
&redirect_uri=<same_as_authorize>
&client_id=<client_id>
&code_verifier=<pkce_verifier>

Refresh token grant

POST /oauth/token HTTP/1.1
Host: api.vivin.app
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=<refresh_token>
&client_id=<client_id>

Refresh tokens live 60 days, rotate on each successful refresh, and bind to the same client + landlord.

Token response

Response (200) — OAuth token envelope (no-store):

{
"access_token": "<management JWT>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<opaque refresh token>",
"scope": "bookings listings properties"
}
FieldMeaning
access_tokenSame management JWT shape as POST /auth/login — use as Authorization: Bearer on Landlord MCP /mcp and Core API routes the user's role permits. JWT includes tokenSource: "mcp" for audit.
expires_inSeconds until JWT expiry (from JWT exp claim)
refresh_tokenOpaque refresh handle — 60-day lifetime, rotates on each refresh grant
scopeSpace-separated tool domains actually granted (intersection of account tool permissions, user role, and consent)

After the initial Bearer exchange, Landlord MCP clients should send Mcp-Session-Id on follow-up streamable HTTP requests — see Landlord MCP — How it connects.

Scopes and tool permissions

Advertised scopes map one-to-one to Landlord MCP tool domains:

OAuth scopeLandlord MCP domain
bookingsBookings tools
listingsListings tools
propertiesProperties tools
tenantsTenants tools
maintenanceMaintenance / tickets tools
paymentsPayments tools
billsBills tools
ownersOwners tools
analyticsAnalytics / KPI tools

Issued scope is the intersection of:

  1. Account Settings → AI / MCP → Tool permissions (account-wide Off / Read-only / Edit),
  2. The consenting user's role permissions and per-user AI access overrides, and
  3. The scope parameter on the authorize request (when present).

A domain set to Off at account level is omitted from the token even if the connector requests it.

Redirect URI policy

At registration and on every authorize / token call, redirect URIs must match the trust policy:

ClassExamples
Anthropic Claude.aihttps://claude.ai/api/mcp/auth_callback
OpenAI ChatGPThttps://chatgpt.com/connector_platform_oauth_redirect, or any https://chatgpt.com/connector/oauth/… path prefix
Loopback (RFC 8252)http://localhost:*/*, http://127.0.0.1:*/* — any port and path

Arbitrary https:// origins, non-HTTPS remotes (except loopback), and data: / javascript: schemes are rejected — this blocks open-redirect token exfiltration.

Revocation and re-authorization

There is no public revoke endpoint for operators today. Access ends when:

  • The refresh token expires (60 days without a successful refresh), or
  • AI access is disabled for the user on Per-user permissions, or
  • Every MCP tool domain is set to Off on refresh — the next grant_type=refresh_token call returns access_denied and the connector must send the landlord through /oauth/authorize again.

Rotate compromised connector sessions by disabling AI access, then re-authorizing after policy is restored.

Error responses

OAuth errors use RFC 6749-style JSON on the token endpoint:

{
"error": "invalid_grant",
"error_description": "Authorization code expired or already used"
}

Common error values: invalid_client, invalid_grant, invalid_request, unsupported_grant_type, access_denied. Authorize-step protocol errors redirect to the registered redirect_uri with error and error_description query parameters when the redirect URI is already trusted.

For management JWT expiry on MCP tool calls (not OAuth refresh), see Error handling.

MCP OAuth section cross-reference

TopicPair with
DiscoveryLandlord MCP — Connector OAuth (operator narrative)
Authorization code flowAI / MCP — Connector OAuth (settings UI)
Token responseManagement session (JWT login contrast), Landlord MCP — How it connects (Mcp-Session-Id)
Scopes and tool permissionsAI / MCP — Tool permissions, Users and roles
Revocation and re-authorizationAI / MCP — Per-user permissions

Setup sequence after go-live

Companion API guides