Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mcp_server_uyuni/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

UYUNI_MCP_HOST = os.environ.get("UYUNI_MCP_HOST", "127.0.0.1")
UYUNI_MCP_PORT = int(os.environ.get("UYUNI_MCP_PORT", "8000"))
UYUNI_AUTH_SERVER = os.environ.get("UYUNI_AUTH_SERVER")
UYUNI_AUTH_SERVER = os.environ.get("UYUNI_AUTH_SERVER", None)

UYUNI_MCP_SSL_VERIFY = (
os.environ.get("UYUNI_MCP_SSL_VERIFY", "true").lower()
Expand Down
92 changes: 92 additions & 0 deletions src/mcp_server_uyuni/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Centralized API exception classes for the MCP server.

This module exposes a small hierarchy of exceptions that describe
transport, HTTP and application-level failures when talking to Uyuni.
"""

class APIError(Exception):
"""Base class for API errors."""


class HTTPError(APIError):
"""HTTP status related error.

Attributes:
status_code: HTTP status code returned by the server.
url: URL that was requested.
body: Optional response body.
"""

def __init__(self, status_code: int, url: str, body: str | None = None):
msg = f"HTTP error {status_code} for {url}. Response body: {body!r}"
super().__init__(msg)
self.status_code = status_code
self.url = url
self.body = body


class AuthError(HTTPError):
"""Authentication-related HTTP error.
"""
# No custom constructor: behave exactly like HTTPError.
pass


class NetworkError(APIError):
"""Network/transport level error.

Represents both timeouts and other connection-level failures. Callers
can inspect the `timed_out` attribute to distinguish timeouts.
"""

def __init__(self, url: str, original: Exception | None = None, timed_out: bool = False):
if timed_out:
msg = (
f"Timeout while contacting Uyuni at {url}. This may indicate a long-running "
"action or network issues. Original: {original}"
)
else:
msg = f"Network error while contacting Uyuni at {url}: {original}"
super().__init__(msg)
self.url = url
self.original = original
self.timed_out = timed_out


class UnexpectedResponse(APIError):
"""Application-level unexpected response from Uyuni.

This represents business-logic level failures returned by the Uyuni API
(for example: resource not found, entity already exists, validation
failures, etc.). The `response` should be a human-readable string with
the message/reason returned by Uyuni.
"""

def __init__(self, url: str, response: str | None = None):
msg = f"Unexpected response from Uyuni at {url}: {response!r}"
super().__init__(msg)
self.url = url
self.response = response


class NotFoundError(APIError):
"""
Raised when an entity/resource is not found in Uyuni or MCP APIs.
Can be used for missing systems, packages, events, etc.
The identifier is either an int or a str.
"""
def __init__(self, what: str, identifier: int | str = None):
msg = f"{what} not found" + (f" (identifier: {identifier})" if identifier is not None else "")
super().__init__(msg)
self.what = what
self.identifier: int | str | None = identifier


__all__ = [
"APIError",
"HTTPError",
"AuthError",
"NetworkError",
"UnexpectedResponse",
"NotFoundError",
]
4 changes: 2 additions & 2 deletions src/mcp_server_uyuni/logging_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import logging
from typing import Union
from mcp_server_uyuni.constants import Transport
from .constants import Transport

def get_logger(
name: str = "mcp_server_uyuni",
Expand Down Expand Up @@ -41,4 +41,4 @@ def get_logger(

logger.addHandler(handler)

return logger
return logger
Loading