Skip to main content
Glama
javiarmesto

Business Central MCP Server

by javiarmesto
client.py6.78 kB
""" client.py Cliente HTTP asíncrono y resiliente para la API de Microsoft Dynamics 365 Business Central. Características principales: - Obtiene y refresca tokens Azure AD automáticamente (OAuth2/Entra ID). - Implementa lógica de reintentos exponenciales y manejo robusto de errores HTTP (401, 5xx). - Expone métodos asíncronos para operaciones clave: * get_customers(top): Lista clientes * get_customer(id): Detalle de cliente * get_items(top): Lista artículos * get_orders(top): Lista órdenes de venta * create_customer(data): Crea un nuevo cliente Onboarding rápido: 1. Asegúrate de que el archivo `.env` esté correctamente configurado (ver README). 2. Usa los métodos del cliente para interactuar con la API de BC desde herramientas MCP. 3. Consulta los docstrings de cada método para ejemplos y detalles de uso. Referencias útiles: - APIs REST de Business Central: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/webservices/api-overview - Seguridad y autenticación: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/enable-apis-using-azure-active-directory - Blog TechSphereDynamics: https://techspheredynamics.com """ import asyncio import httpx import logging import os from typing import Any, Dict, List, Optional from config import config from azure_auth import token_manager # Configuración global de logging LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() logging.basicConfig( level=LOG_LEVEL, format="%(asctime)s %(levelname)s %(name)s: %(message)s" ) logger = logging.getLogger("bc_client") class BusinessCentralClient: """ Cliente asíncrono para la API de Business Central. Gestiona autenticación, reintentos y expone métodos de negocio clave. """ def __init__(self): self.base = config.bc.base_url self.comp = config.bc.company_id self._retries = 3 # Número de reintentos ante errores transitorios self._timeout = 30 # Timeout global para peticiones HTTP (segundos) async def _request( self, method: str, path: str, params: Optional[Dict] = None, data: Optional[Dict] = None ) -> Optional[Dict[str, Any]]: """ Realiza una petición HTTP autenticada a la API de Business Central. Maneja reintentos automáticos ante errores 401/5xx y refresca el token si es necesario. Parámetros: method (str): Método HTTP ('GET', 'POST', etc.) path (str): Ruta relativa dentro de la compañía BC params (dict): Parámetros de query opcionales data (dict): Payload JSON para POST/PUT Retorna: Diccionario con la respuesta JSON o None si falla. """ url = f"{self.base}/companies({self.comp})/{path}" for i in range(self._retries): # DEBUG: mostrar intento de solicitud url = f"{self.base}/companies({self.comp})/{path}" logger.debug(f"BC Request #{i+1}: {method} {url} params={params} data={data}") token = await token_manager.get_token() if not token: logger.error("No se pudo obtener el token de autenticación.") return None headers = { "Authorization": f"Bearer {token}", "Accept": "application/json" } async with httpx.AsyncClient(timeout=self._timeout) as cli: resp = await cli.request(method, url, headers=headers, params=params, json=data) # DEBUG: mostrar respuesta logger.debug(f"BC Response {resp.status_code}: {resp.text[:200]}") if resp.status_code in (200, 201): return resp.json() if resp.status_code == 401: token_manager._token = None logger.warning("Token expirado o inválido. Reintentando...") continue if resp.status_code >= 500: logger.warning(f"Error {resp.status_code} en Business Central. Reintentando...") await asyncio.sleep(2 ** i) continue break logger.error(f"BC API {method} {path}: {resp.status_code}") return None async def get_customers(self, top: int = 20) -> List[Dict]: """ Obtiene una lista de clientes de Business Central. Parámetros: top (int): Número máximo de clientes a retornar (default 20). Retorna: Lista de diccionarios con clientes. """ res = await self._request("GET", f"customers", params={"$top": top}) if res: logger.info(f"Clientes recuperados: {len(res.get('value', []))}") else: logger.error("No se pudo recuperar la lista de clientes.") return res.get("value", []) if res else [] async def get_customer(self, cid: str) -> Optional[Dict]: """ Obtiene el detalle de un cliente por su ID. Parámetros: cid (str): ID único del cliente en BC. Retorna: Diccionario con los datos del cliente o None si no existe. """ return await self._request("GET", f"customers({cid})") async def get_items(self, top: int = 20) -> List[Dict]: """ Lista artículos de Business Central. Parámetros: top (int): Número máximo de artículos a retornar (default 20). Retorna: Lista de diccionarios con artículos. """ res = await self._request("GET", "items", params={"$top": top}) return res.get("value", []) if res else [] async def get_orders(self, top: int = 10) -> List[Dict]: """ Lista órdenes de venta de Business Central. Parámetros: top (int): Número máximo de órdenes a retornar (default 10). Retorna: Lista de diccionarios con órdenes de venta. """ res = await self._request("GET", "salesOrders", params={"$top": top}) return res.get("value", []) if res else [] async def create_customer(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]: """ Crea un nuevo cliente en Business Central. Parámetros: data (dict): Diccionario con los campos del nuevo cliente según la API de BC. Retorna: Respuesta JSON del nuevo cliente o None en caso de error. Notas: - Consulta la documentación oficial para el esquema de datos requerido. """ # POST a la colección 'customers' return await self._request("POST", "customers", data=data) # Instancia compartida para uso global bc_client = BusinessCentralClient()

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