Skip to main content
Glama
javiarmesto

Business Central MCP Server

by javiarmesto
azure_auth.py4.8 kB
""" azure_auth.py Objetivo: --------- Gestionar la autenticación OAuth2 con Azure AD (Entra ID) usando el flujo Client Credentials, incluyendo cache de tokens y helpers asíncronos para obtener y renovar el token de acceso. Este módulo centraliza la obtención de tokens para acceder a la API de Business Central desde Python, siguiendo las mejores prácticas de seguridad y eficiencia. Referencias: - https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow - https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/azure-active-directory """ # ============================= # IMPORTS Y DEPENDENCIAS # ============================= import asyncio import httpx from datetime import datetime, timedelta from typing import Optional from config import config # ============================= # CLASE PRINCIPAL DE GESTIÓN DE TOKENS # ============================= class AzureTokenManager: def __init__(self): # Token actual y expiración self._token: Optional[str] = None self._expires: Optional[datetime] = None # Scope de acceso para Business Central self._scope = "https://api.businesscentral.dynamics.com/.default" # ============================= # MÉTODO PRIVADO: ¿Token válido? # ============================= def _valid(self) -> bool: return self._token and self._expires and datetime.utcnow() < self._expires # ============================= # OBTENER TOKEN (público, preferido) # ============================= async def get_token(self) -> Optional[str]: """ Devuelve un token válido, renovando si es necesario. """ if self._valid(): return self._token return await self._fetch() # ============================= # MÉTODO PRIVADO: Solicitar nuevo token a Azure AD # ============================= async def _fetch(self) -> Optional[str]: url = f"{config.azure_ad.authority}/oauth2/v2.0/token" data = { "grant_type": "client_credentials", "client_id": config.azure_ad.client_id, "client_secret": config.azure_ad.client_secret, "scope": self._scope } headers = {"Content-Type": "application/x-www-form-urlencoded"} async with httpx.AsyncClient() as cli: resp = await cli.post(url, data=data, headers=headers, timeout=30) if resp.status_code == 200: j = resp.json() self._token = j["access_token"] self._expires = datetime.utcnow() + timedelta(seconds=j.get("expires_in", 3600)) return self._token print(f"[ERROR] Token Azure AD: {resp.status_code}") return None # ============================= # MÉTODO ALTERNATIVO: Solicitar token (simula Postman) # ============================= async def _acquire_new_token(self) -> Optional[str]: """ Adquiere un nuevo token de Azure AD usando un body URL-encoded (útil para debugging avanzado). """ token_url = f"{config.azure_ad.authority}/oauth2/v2.0/token" # Construimos el body como URL-encoded form = { "grant_type": "client_credentials", "client_id": config.azure_ad.client_id, "client_secret": config.azure_ad.client_secret, "scope": self._scope } headers = {"Content-Type": "application/x-www-form-urlencoded"} try: async with httpx.AsyncClient(timeout=30.0) as client: # USAR content= y urllib.parse.urlencode para simular Postman exactamente import urllib.parse payload = urllib.parse.urlencode(form) response = await client.post( token_url, content=payload, headers=headers ) # DEBUG: si falla, ver body completo if response.status_code != 200: print(f"[DEBUG] Token request failed ({response.status_code}): {response.text}") return None data = response.json() access_token = data["access_token"] expires_in = data.get("expires_in", 3600) # cache self._token_cache = access_token self._token_expires = datetime.now() + timedelta(seconds=expires_in - 300) return access_token except Exception as e: print(f"[ERROR] Exception acquiring token: {e}") return None # ============================= # SINGLETON GLOBAL PARA USO EN TODO EL PROYECTO # ============================= token_manager = AzureTokenManager()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/javiarmesto/Lab3_1_MCP_BusinessCentral'

If you have feedback or need assistance with the MCP directory API, please join our Discord server