For the complete documentation index, see llms.txt. This page is also available as Markdown.

OAuth

How Kula Intelligence implements OAuth 2.1 for MCP clients — discovery, dynamic client registration, PKCE, scopes, and audience binding.

Kula Intelligence ships a standards-based OAuth 2.1 authorization server so MCP clients can sign a user in and receive an access token, with no token-pasting. It implements the discovery and security RFCs the MCP ecosystem relies on.

For end users, the flow is invisible — they click "connect" and sign in. This page is the detail for client developers.

Not every client does OAuth. Clients without a custom-connector flow (ChatGPT, Cursor, Lovable, the Claude API, custom agents) connect with a role-scoped access token minted in app.kula.digital instead — the same minted MCP credential, presented as a bearer header against https://mcp.kula.digital/mcp. See Connect. This page covers the OAuth path used by Claude's custom connector.

The two hosts

Role
Host
Serves

Resource server (the MCP server)

mcp.kula.digital

the per-connector resource /connect/{…}/mcp, and its protected-resource metadata

Authorization server

app.kula.digital

discovery, registration, authorize, token; connectors are created here

Clients are pointed at a role-scoped connect link (https://mcp.kula.digital/connect/{…}/mcp) created in app.kula.digital — there is no open /mcp endpoint to connect to. The resource advertises the authorization server, so a compliant client only needs the connect link to discover everything else. For a connect link, the protected-resource metadata is path-scoped: /.well-known/oauth-protected-resource/connect/{…}/mcp.

Discovery

  1. Protected-resource metadata (RFC 9728):

    GET https://mcp.kula.digital/.well-known/oauth-protected-resource

    Returns the canonical resource URL and the authorization_servers list (pointing at app.kula.digital).

  2. Authorization-server metadata (RFC 8414):

    GET https://app.kula.digital/.well-known/oauth-authorization-server

    Returns the authorization_endpoint, token_endpoint, registration_endpoint, code_challenge_methods_supported (["S256"]), grant_types_supported (authorization_code, refresh_token), and scopes_supported.

Dynamic client registration (RFC 7591)

Clients may self-register:

Returns a client_id (and a secret only for confidential clients; public clients use token_endpoint_auth_method: "none" with PKCE).

For Claude's hosted clients, register the callback https://claude.ai/api/mcp/auth_callback. For Claude Code, loopback redirects on http://localhost:<port> are accepted with port-agnostic matching.

Authorization + token

  • Authorize (GET /oauth/authorize) requires response_type=code, code_challenge, and code_challenge_method=S256 (PKCE is mandatory). The user is signed in (delegated to our identity provider), consents, and is redirected back with a code.

  • Token (POST /oauth/token) accepts application/x-www-form-urlencoded for the authorization_code grant (with code_verifier) and the refresh_token grant. Refresh tokens are rotated on use. Responses carry Cache-Control: no-store.

The access token is the same minted MCP token the runtime verifies — OAuth adds consent and discovery without introducing a new token type.

Scopes

The OAuth scope maps to Kula's permission levels:

Scope
Means

analytics

Aggregates only; no personal data

operations

Member/staff names visible; contact + payment details hidden

admin

Emails/phones visible; payment details hidden

full

Everything; every call recorded

offline_access

Issue a refresh token

Request the lowest scope the integration needs.

Audience binding

Tokens are bound to the canonical MCP resource URL (RFC 8707 resource indicator). The runtime verifies the token's audience, so a token minted for Kula can't be replayed against a different server.

Revocation

Connectors are revocable from app.kula.digital and stop working within about a minute. Clients should handle a 401 by re-running the OAuth flow; if the connect link itself has been revoked or used up, the user creates a fresh connector.

Last updated

Was this helpful?