Skip to main content
Glama

cBioPortal MCP Server

by pickleton89
api_client.py7.49 kB
import logging from typing import Any, Dict, Optional import httpx logger = logging.getLogger(__name__) class APIClientError(Exception): """Base exception for API client errors.""" pass class APIHTTPError(APIClientError): """Raised when an HTTP error occurs during API request.""" def __init__( self, message: str, status_code: int, response_text: str = "", endpoint: str = "", ): super().__init__(message) self.status_code = status_code self.response_text = response_text self.endpoint = endpoint class APINetworkError(APIClientError): """Raised when a network/request error occurs during API request.""" def __init__(self, message: str, endpoint: str = ""): super().__init__(message) self.endpoint = endpoint class APITimeoutError(APINetworkError): """Raised when a timeout occurs during API request.""" pass class APIParseError(APIClientError): """Raised when there's an error parsing the API response.""" def __init__(self, message: str, endpoint: str = "", response_text: str = ""): super().__init__(message) self.endpoint = endpoint self.response_text = response_text class APIClient: """ A client for making asynchronous API requests to the cBioPortal API. Manages an httpx.AsyncClient instance. """ def __init__( self, base_url: str = "https://www.cbioportal.org/api", client_timeout: float = 480.0, ): """ Initializes the APIClient. Args: base_url: The base URL for the cBioPortal API. client_timeout: Timeout in seconds for the HTTP client. """ self.base_url = base_url.rstrip("/") self.client_timeout = client_timeout self._client: Optional[httpx.AsyncClient] = None logger.debug( f"APIClient initialized with base_url: {self.base_url}, timeout: {self.client_timeout}" ) async def startup(self): """ Initializes the asynchronous HTTP client. Should be called before making any API requests. """ if self._client is None: self._client = httpx.AsyncClient( base_url=self.base_url, timeout=self.client_timeout ) logger.info( f"APIClient's httpx.AsyncClient started with base_url: {self.base_url} and timeout: {self.client_timeout}s" ) else: logger.info("APIClient's httpx.AsyncClient was already started.") async def shutdown(self): """ Closes the asynchronous HTTP client. Should be called when the client is no longer needed. """ if self._client: await self._client.aclose() self._client = None # Mark as closed logger.info("APIClient's httpx.AsyncClient closed.") else: logger.info( "APIClient's httpx.AsyncClient was already closed or not initialized." ) async def make_api_request( self, endpoint: str, method: str = "GET", params: Optional[Dict[str, Any]] = None, json_data: Optional[Any] = None, ) -> Any: """ Makes an asynchronous API request to the cBioPortal API. Args: endpoint: The API endpoint path (e.g., "studies"). method: HTTP method, "GET" or "POST". params: URL query parameters. json_data: JSON body for POST requests. Returns: The JSON response from the API. Raises: RuntimeError: If the client is not started. ValueError: If an unsupported HTTP method is provided. Exception: For API request failures (HTTP errors, request errors, etc.). """ if self._client is None: raise RuntimeError( "APIClient._client is not initialized. Call APIClient.startup() before making requests." ) # Use relative path since base_url is configured in the client endpoint_path = endpoint.lstrip("/") logger.debug( f"Making {method.upper()} request to {endpoint_path} with params: {params}, json_data: {json_data is not None}" ) try: if method.upper() == "GET": response = await self._client.get(endpoint_path, params=params) elif method.upper() == "POST": response = await self._client.post( endpoint_path, json=json_data, params=params ) else: logger.error( f"Unsupported HTTP method: {method} for endpoint: {endpoint_path}" ) raise ValueError(f"Unsupported HTTP method: {method}") response.raise_for_status() # Raises HTTPStatusError for 4xx/5xx responses if not response.text: # Handle empty response body logger.debug( f"Empty response body from {response.url} (status: {response.status_code}). Endpoint: {endpoint}" ) # Original logic: if endpoint implies a list (plural 's' or 'fetch'), return empty list. if endpoint.endswith("s") or endpoint.endswith("fetch"): return [] return {} # Otherwise, return empty dict. return response.json() except httpx.HTTPStatusError as e: error_text_snippet = ( e.response.text[:500] if e.response.text else "No response body" ) logger.error( f"HTTP error {e.response.status_code} for {method.upper()} {endpoint_path}: {error_text_snippet}..." ) raise APIHTTPError( f"API request to {endpoint} failed with status {e.response.status_code}: {e.response.text}", status_code=e.response.status_code, response_text=e.response.text, endpoint=endpoint, ) from e except httpx.TimeoutException as e: logger.error( f"Timeout error for {method.upper()} {endpoint_path}: {str(e)}" ) raise APITimeoutError( f"API request to {endpoint} timed out: {str(e)}", endpoint=endpoint ) from e except httpx.RequestError as e: # Catches network errors, etc. logger.error( f"Request error for {method.upper()} {endpoint_path}: {str(e)}" ) raise APINetworkError( f"API request to {endpoint} failed due to a network error: {str(e)}", endpoint=endpoint, ) from e except (ValueError, TypeError) as e: # JSON decode errors, etc. logger.error( f"Parse error during API request to {method.upper()} {endpoint_path}: {str(e)}" ) raise APIParseError( f"Failed to parse response from {endpoint}: {str(e)}", endpoint=endpoint ) from e except Exception as e: # Catch-all for other unexpected errors logger.error( f"Unexpected error during API request to {method.upper()} {endpoint_path}: {str(e)}" ) raise APIClientError( f"Unexpected error during API request to {endpoint}: {str(e)}" ) from e

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/pickleton89/cbioportal-mcp'

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