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

Abstract methods

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.
redirect_uri
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.
state
str
Opaque value for CSRF protection. Include in the URL.

exchange

Exchange an authorization code for tokens.
code
str
Authorization code from the OAuth callback.
redirect_uri
str
Same redirect URI used in get_url().
Returns TokenResponse with at least access_token.

refresh

Refresh an expired access token.
refresh_token
str
Refresh token from a previous exchange.
Returns TokenResponse with new access_token.

get_nickname

Return a display name for the connected account (shown in UI).
access_token
str
Valid access token.
Return something recognizable: email, username, or organization name.

Optional methods

revoke

Revoke an access token when user disconnects.
async def revoke(self, access_token: str) -> bool:
    """Revoke the token. Return True if successful."""
    # Default: returns True (no-op)

TokenResponse

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

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"]