Skip to content

Commit 16cb269

Browse files
authored
Merge pull request #19 from flacatus/fix_e2e
feat: Improve e2e tests errors and incidents tools
2 parents dffa7ab + bbfc57a commit 16cb269

5 files changed

Lines changed: 86 additions & 19 deletions

File tree

konflux-devlake-mcp.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ async def run_server(config: KonfluxDevLakeConfig) -> int:
199199
"port": config.server.port,
200200
"timeout_keep_alive": config.server.timeout_keep_alive,
201201
"timeout_graceful_shutdown": config.server.timeout_graceful_shutdown,
202+
"config": config,
202203
}
203204
if config.server.transport == "http"
204205
else {}
@@ -226,9 +227,17 @@ async def run_server(config: KonfluxDevLakeConfig) -> int:
226227
logger.info("Server shutdown complete")
227228
except (asyncio.CancelledError, KeyboardInterrupt):
228229
# Ignore cancellation errors during shutdown
229-
logger.debug("Shutdown interrupted")
230+
pass
231+
except (ValueError, OSError):
232+
# Ignore I/O errors during shutdown (handlers may be closed)
233+
pass
230234
except Exception as e:
231-
logger.error(f"Error during shutdown: {e}")
235+
# Only log non-I/O errors during shutdown
236+
try:
237+
logger.error(f"Error during shutdown: {e}")
238+
except (ValueError, OSError):
239+
# If logging fails, just pass silently
240+
pass
232241

233242

234243
async def main():

server/factory/server_factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,13 @@ def create_transport(self, transport_type: str, **kwargs) -> BaseTransport:
8585
port = kwargs.get("port", 3000)
8686
timeout_keep_alive = kwargs.get("timeout_keep_alive", 300)
8787
timeout_graceful_shutdown = kwargs.get("timeout_graceful_shutdown", 60)
88+
config = kwargs.get("config")
8889
return HttpTransport(
8990
host=host,
9091
port=port,
9192
timeout_keep_alive=timeout_keep_alive,
9293
timeout_graceful_shutdown=timeout_graceful_shutdown,
94+
config=config,
9395
)
9496
else:
9597
raise ValueError(f"Unsupported transport type: {transport_type}")

server/transport/http_transport.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(
3030
port: int = 3000,
3131
timeout_keep_alive: int = 600,
3232
timeout_graceful_shutdown: int = 120,
33+
config=None,
3334
):
3435
"""
3536
Initialize the HTTP transport.
@@ -39,11 +40,13 @@ def __init__(
3940
port: Port number to listen on
4041
timeout_keep_alive: Keep-alive timeout in seconds (default: 600 for LLM connections)
4142
timeout_graceful_shutdown: Graceful shutdown timeout in seconds (default: 120)
43+
config: Optional configuration object for security manager
4244
"""
4345
self.host = host
4446
self.port = port
4547
self.timeout_keep_alive = timeout_keep_alive
4648
self.timeout_graceful_shutdown = timeout_graceful_shutdown
49+
self.config = config
4750
self.logger = get_logger(f"{__name__}.HttpTransport")
4851
self._session_manager = None
4952
self._server = None
@@ -174,6 +177,50 @@ async def wrapped_handle_request(scope, receive, send):
174177
session_manager.handle_request = wrapped_handle_request
175178
return session_manager
176179

180+
def _create_health_endpoints(self):
181+
"""Create ASGI app for health check and security endpoints."""
182+
from starlette.applications import Starlette
183+
from starlette.responses import JSONResponse
184+
from starlette.routing import Route
185+
from utils.security import KonfluxDevLakeSecurityManager
186+
187+
# Create a minimal config object if not provided
188+
if self.config is None:
189+
190+
class MinimalConfig:
191+
allowed_ips = []
192+
api_keys = {}
193+
194+
config = MinimalConfig()
195+
else:
196+
config = self.config
197+
198+
security_manager = KonfluxDevLakeSecurityManager(config)
199+
200+
async def health_check(request):
201+
"""Health check endpoint."""
202+
return JSONResponse(
203+
{
204+
"status": "healthy",
205+
"service": "konflux-devlake-mcp-server",
206+
"transport": "http",
207+
}
208+
)
209+
210+
async def security_stats(request):
211+
"""Security statistics endpoint."""
212+
stats = security_manager.get_security_stats()
213+
return JSONResponse(stats)
214+
215+
app = Starlette(
216+
routes=[
217+
Route("/health", health_check, methods=["GET"]),
218+
Route("/security/stats", security_stats, methods=["GET"]),
219+
]
220+
)
221+
222+
return app
223+
177224
def _create_mcp_app(self, app):
178225
"""Create ASGI app that handles MCP requests with improved error handling."""
179226
from starlette.responses import Response

tools/devlake/incident_tools.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ def get_tools(self) -> List[Tool]:
4949
"from the Konflux DevLake database with advanced filtering capabilities. "
5050
"This tool automatically deduplicates incidents by incident_key to show "
5151
"only the most recent version of each incident. Supports filtering by "
52-
"status (e.g., 'DONE', 'IN_PROGRESS', 'OPEN'), component name, and "
53-
"flexible date ranges. Provides comprehensive incident data including "
54-
"incident_key, title, description, status, created_date, "
55-
"resolution_date, lead_time_minutes, component, and URL. Perfect for "
56-
"incident analysis, reporting, and understanding operational issues. "
57-
"Returns incidents sorted by creation date (newest first)."
52+
"status (e.g., 'DONE', 'IN_PROGRESS', 'OPEN'), component name, and flexible "
53+
"date ranges. Provides comprehensive incident data including incident_key, "
54+
"title, description, status, created_date, resolution_date, "
55+
"lead_time_minutes, component, and URL. Perfect for incident analysis, "
56+
"reporting, and understanding operational issues. Returns incidents sorted "
57+
"by creation date (newest first)."
5858
),
5959
inputSchema={
6060
"type": "object",

utils/logger.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ def _setup_logging() -> logging.Logger:
8787
# Clear existing handlers
8888
root_logger.handlers.clear()
8989

90-
# Console handler with filter
91-
console_handler = logging.StreamHandler(sys.stdout)
90+
# Console handler with filter - use stderr for stdio transport compatibility
91+
# When using stdio transport, stdout must only contain JSONRPC messages
92+
console_handler = logging.StreamHandler(sys.stderr)
9293
console_handler.setLevel(logging.INFO)
9394
console_handler.setFormatter(formatter)
9495
console_handler.addFilter(closed_resource_filter)
@@ -218,16 +219,24 @@ def log_tool_call(tool_name: str, arguments: dict = None, success: bool = True,
218219

219220
def shutdown_logging():
220221
"""Shutdown logging system"""
221-
logger = get_logger(__name__)
222-
logger.info("Shutting down logging system")
223-
224-
# Flush all handlers
225-
root_logger = logging.getLogger()
226-
for handler in root_logger.handlers:
227-
handler.flush()
228-
handler.close()
222+
try:
223+
logger = get_logger(__name__)
224+
logger.info("Shutting down logging system")
229225

230-
logger.info("Logging system shutdown complete")
226+
# Flush all handlers
227+
root_logger = logging.getLogger()
228+
for handler in root_logger.handlers:
229+
try:
230+
handler.flush()
231+
handler.close()
232+
except (ValueError, OSError):
233+
# Ignore errors when closing handlers (file may already be closed)
234+
pass
235+
236+
logger.info("Logging system shutdown complete")
237+
except (ValueError, OSError):
238+
# Ignore errors during shutdown (handlers may already be closed)
239+
pass
231240

232241

233242
class LoggerMixin:

0 commit comments

Comments
 (0)