Skip to main content

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