> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gumstack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AuthProvider

> Implement OAuth authentication for your MCP server

`AuthProvider` is the abstract base class for OAuth integrations. Implement it to let users connect their accounts.

## Abstract methods

```python theme={"dark"}
from mcp.gumstack import AuthProvider, TokenResponse

class MyProvider(AuthProvider):
    name = "my-provider"  # Unique identifier
    
    def get_url(self, redirect_uri: str, state: str) -> str:
        """Return the OAuth authorization URL."""
        ...
    
    async def exchange(self, code: str, redirect_uri: str) -> TokenResponse:
        """Exchange authorization code for tokens."""
        ...
    
    async def refresh(self, refresh_token: str) -> TokenResponse:
        """Refresh an expired access token."""
        ...
    
    async def get_nickname(self, access_token: str) -> str:
        """Return display name for the connected account."""
        ...
```

### name

Unique identifier for this provider. Used in routes and credential storage.

### get\_url

Generate the OAuth authorization URL.

<ParamField body="redirect_uri" type="str">
  Callback URL where the provider redirects after authorization. Gumstack's callback URL is `https://api.gumstack.com/auth/callback` — use this when registering your OAuth app with the third-party service.
</ParamField>

<ParamField body="state" type="str">
  Opaque value for CSRF protection. Include in the URL.
</ParamField>

### exchange

Exchange an authorization code for tokens.

<ParamField body="code" type="str">
  Authorization code from the OAuth callback.
</ParamField>

<ParamField body="redirect_uri" type="str">
  Same redirect URI used in `get_url()`.
</ParamField>

Returns `TokenResponse` with at least `access_token`.

### refresh

Refresh an expired access token.

<ParamField body="refresh_token" type="str">
  Refresh token from a previous exchange.
</ParamField>

Returns `TokenResponse` with new `access_token`.

### get\_nickname

Return a display name for the connected account (shown in UI).

<ParamField body="access_token" type="str">
  Valid access token.
</ParamField>

Return something recognizable: email, username, or organization name.

## Optional methods

### revoke

Revoke an access token when user disconnects.

```python theme={"dark"}
async def revoke(self, access_token: str) -> bool:
    """Revoke the token. Return True if successful."""
    # Default: returns True (no-op)
```

## TokenResponse

```python theme={"dark"}
from mcp.gumstack import TokenResponse

TokenResponse(
    access_token: str,           # Required
    refresh_token: str | None,   # For token refresh
    expires_in: int | None,      # Seconds until expiry
    token_type: str = "Bearer",
    scope: str | None = None
)
```

## Example: Linear

```python theme={"dark"}
import os
import httpx
from mcp.gumstack import AuthProvider, TokenResponse

class LinearAuthProvider(AuthProvider):
    name = "linear"
    
    def get_url(self, redirect_uri: str, state: str) -> str:
        return (
            f"https://linear.app/oauth/authorize"
            f"?client_id={os.environ['LINEAR_CLIENT_ID']}"
            f"&redirect_uri={redirect_uri}"
            f"&state={state}"
            f"&response_type=code"
            f"&scope=read,write"
        )
    
    async def exchange(self, code: str, redirect_uri: str) -> TokenResponse:
        async with httpx.AsyncClient() as client:
            resp = await client.post(
                "https://api.linear.app/oauth/token",
                data={
                    "grant_type": "authorization_code",
                    "client_id": os.environ["LINEAR_CLIENT_ID"],
                    "client_secret": os.environ["LINEAR_CLIENT_SECRET"],
                    "redirect_uri": redirect_uri,
                    "code": code,
                }
            )
            data = resp.json()
            return TokenResponse(
                access_token=data["access_token"],
                expires_in=data.get("expires_in")
            )
    
    async def refresh(self, refresh_token: str) -> TokenResponse:
        # Linear tokens don't expire, but implement if needed
        raise NotImplementedError("Linear tokens don't expire")
    
    async def get_nickname(self, access_token: str) -> str:
        async with httpx.AsyncClient() as client:
            resp = await client.post(
                "https://api.linear.app/graphql",
                headers={"Authorization": f"Bearer {access_token}"},
                json={"query": "{ viewer { email } }"}
            )
            return resp.json()["data"]["viewer"]["email"]
```
