Auth manager added that automatically refreshes expired tokens

This commit is contained in:
George Antonopoulos
2026-01-08 21:14:19 +00:00
parent 2f1800e792
commit 6265026f57
3 changed files with 84 additions and 6 deletions

70
auth_manager.py Normal file
View File

@@ -0,0 +1,70 @@
"""
Auth manager for Basecamp MCP server.
Handles token refresh logic automatically.
"""
import logging
import os
import token_storage
from basecamp_oauth import BasecampOAuth
from datetime import datetime
logger = logging.getLogger(__name__)
def ensure_authenticated():
"""
Checks if the current token is valid and refreshes it if necessary.
Returns:
bool: True if authenticated (or successfully refreshed), False otherwise.
"""
token_data = token_storage.get_token()
if not token_data or not token_data.get('access_token'):
logger.error("No token data found. Initial authentication required.")
return False
if not token_storage.is_token_expired():
logger.debug("Token is still valid.")
return True
# Token is expired, try to refresh
refresh_token = token_data.get('refresh_token')
if not refresh_token:
logger.error("Token expired and no refresh token available.")
return False
logger.info("Token expired. Attempting automatic refresh...")
try:
oauth = BasecampOAuth()
new_token_data = oauth.refresh_token(refresh_token)
# Basecamp refresh response usually contains access_token, expires_in.
# It may or may not contain a new refresh_token.
new_access_token = new_token_data.get('access_token')
new_refresh_token = new_token_data.get('refresh_token') or refresh_token
expires_in = new_token_data.get('expires_in')
# Maintain the account_id we already had
account_id = token_data.get('account_id')
token_storage.store_token(
access_token=new_access_token,
refresh_token=new_refresh_token,
expires_in=expires_in,
account_id=account_id
)
logger.info("Successfully refreshed and stored new tokens.")
return True
except Exception as e:
logger.error(f"Failed to refresh token: {e}")
return False
if __name__ == "__main__":
# Can be run as a standalone script to manually force a refresh check
logging.basicConfig(level=logging.INFO)
if ensure_authenticated():
print("Authenticated!")
else:
print("Authentication failed.")

View File

@@ -18,6 +18,7 @@ from mcp.server.fastmcp import FastMCP
from basecamp_client import BasecampClient from basecamp_client import BasecampClient
from search_utils import BasecampSearch from search_utils import BasecampSearch
import token_storage import token_storage
import auth_manager
from dotenv import load_dotenv from dotenv import load_dotenv
# Determine project root (directory containing this script) # Determine project root (directory containing this script)
@@ -51,11 +52,14 @@ def _get_basecamp_client() -> Optional[BasecampClient]:
logger.error("No OAuth token available") logger.error("No OAuth token available")
return None return None
# Check if token is expired # Check and automatically refresh if token is expired
if token_storage.is_token_expired(): if not auth_manager.ensure_authenticated():
logger.error("OAuth token has expired") logger.error("OAuth token has expired and automatic refresh failed")
return None return None
# Get fresh token data after potential refresh
token_data = token_storage.get_token()
# Get account_id from token data first, then fall back to env var # Get account_id from token data first, then fall back to env var
account_id = token_data.get('account_id') or os.getenv('BASECAMP_ACCOUNT_ID') account_id = token_data.get('account_id') or os.getenv('BASECAMP_ACCOUNT_ID')
user_agent = os.getenv('USER_AGENT') or "Basecamp MCP Server (cursor@example.com)" user_agent = os.getenv('USER_AGENT') or "Basecamp MCP Server (cursor@example.com)"

View File

@@ -13,6 +13,7 @@ from typing import Any, Dict, List, Optional
from basecamp_client import BasecampClient from basecamp_client import BasecampClient
from search_utils import BasecampSearch from search_utils import BasecampSearch
import token_storage import token_storage
import auth_manager
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
@@ -715,11 +716,14 @@ class MCPServer:
logger.error("No OAuth token available") logger.error("No OAuth token available")
return None return None
# Check if token is expired # Check and automatically refresh if token is expired
if token_storage.is_token_expired(): if not auth_manager.ensure_authenticated():
logger.error("OAuth token has expired") logger.error("OAuth token has expired and automatic refresh failed")
return None return None
# Get fresh token data after potential refresh
token_data = token_storage.get_token()
# Get account_id from token data first, then fall back to env var # Get account_id from token data first, then fall back to env var
account_id = token_data.get('account_id') or os.getenv('BASECAMP_ACCOUNT_ID') account_id = token_data.get('account_id') or os.getenv('BASECAMP_ACCOUNT_ID')