Skip to main content
Glama

FastMCP Todo Server

middleware.py7.77 kB
from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import Response, JSONResponse from starlette.types import ASGIApp import logging import asyncio import anyio import inspect import functools import time logger = logging.getLogger(__name__) class EnhancedLoggingMiddleware(BaseHTTPMiddleware): """ Middleware to log requests and responses, providing more context than default logging. """ def __init__(self, app: ASGIApp, logger: logging.Logger): super().__init__(app) self.logger = logger async def dispatch(self, request, call_next): start_time = time.time() # Log request details self.logger.info(f"Request: {request.method} {request.url.path}") response = await call_next(request) process_time = (time.time() - start_time) * 1000 # Log response details self.logger.info( f"Response: {response.status_code} " f"({request.method} {request.url.path}) " f"took {process_time:.2f}ms" ) return response # Placeholder for rate limit middleware async def rate_limit_middleware(request, call_next): """ This is a placeholder for a rate limiting middleware. It currently does nothing but can be expanded later. """ # TODO: Implement actual rate limiting logic response = await call_next(request) return response class ConnectionErrorsMiddleware(BaseHTTPMiddleware): """ Middleware to handle various connection-related errors that occur when clients disconnect. This handles common exceptions from SSE connections and long-polling requests. """ async def dispatch(self, request, call_next): try: return await call_next(request) except RuntimeError as exc: if str(exc) == 'No response returned.' and await request.is_disconnected(): logger.debug("Client disconnected with 'No response returned' error") return Response(status_code=204) raise except (anyio.WouldBlock, asyncio.exceptions.CancelledError) as exc: # These are common with SSE and WebSocket connections when clients disconnect if await request.is_disconnected(): logger.debug(f"Handled {type(exc).__name__} for disconnected client") return Response(status_code=204) raise except ConnectionResetError as exc: logger.debug("Client connection was reset") return Response(status_code=204) except ConnectionAbortedError as exc: logger.debug("Client connection was aborted") return Response(status_code=204) # Keep the old middleware for backward compatibility SuppressNoResponseReturnedMiddleware = ConnectionErrorsMiddleware class NoneTypeResponseMiddleware(BaseHTTPMiddleware): """ Middleware to handle 'NoneType' object is not callable errors. This occurs when a route handler returns None instead of a proper response object. """ async def dispatch(self, request, call_next): try: response = await call_next(request) if response is None: # Log the path that returned None logger.debug(f"Route handler for {request.url.path} returned None instead of a response object") # Return a generic 204 No Content response return Response(status_code=204) return response except TypeError as exc: if "'NoneType' object is not callable" in str(exc): logger.debug(f"Caught NoneType error in {request.url.path}: {str(exc)}") return Response( status_code=204, headers=[(b"content-type", b"text/plain")] ) raise def create_asgi_error_handler(app): """ Create a low-level ASGI wrapper that handles errors and provides fallbacks. This is a more aggressive approach that wraps the entire ASGI application to handle errors at the lowest level of the stack. """ @functools.wraps(app) async def error_handling_app(scope, receive, send): # Define a patched send function original_send = send async def patched_send(message): try: await original_send(message) except Exception as e: logger.debug(f"Error in send: {type(e).__name__}: {str(e)}") # Send a fallback response if possible if message.get("type") == "http.response.start": try: await original_send({ "type": "http.response.start", "status": 204, "headers": [(b"content-type", b"text/plain")] }) await original_send({ "type": "http.response.body", "body": b"", "more_body": False }) except Exception as e2: logger.debug(f"Failed to send fallback response: {str(e2)}") try: # Run the main application await app(scope, receive, patched_send) except TypeError as e: if "'NoneType' object is not callable" in str(e): logger.debug(f"Caught NoneType error at ASGI level: {str(e)}") try: # Send a fallback response await send({ "type": "http.response.start", "status": 204, "headers": [(b"content-type", b"text/plain")] }) await send({ "type": "http.response.body", "body": b"", "more_body": False }) except Exception as e2: logger.debug(f"Failed to send fallback response after NoneType error: {str(e2)}") else: logger.warning(f"Unhandled TypeError: {str(e)}") # Try to send a 500 response try: await send({ "type": "http.response.start", "status": 500, "headers": [(b"content-type", b"text/plain")] }) await send({ "type": "http.response.body", "body": b"Internal Server Error", "more_body": False }) except Exception: pass # We tried our best except (asyncio.exceptions.CancelledError, anyio.WouldBlock) as e: logger.debug(f"Connection error: {type(e).__name__}") # These are common with client disconnections, so we just log and don't try to respond except Exception as e: logger.warning(f"Unhandled exception in ASGI app: {type(e).__name__}: {str(e)}") # Try to send a 500 response try: await send({ "type": "http.response.start", "status": 500, "headers": [(b"content-type", b"text/plain")] }) await send({ "type": "http.response.body", "body": b"Internal Server Error", "more_body": False }) except Exception: pass # We tried our best return error_handling_app

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/MadnessEngineering/fastmcp-todo-server'

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