diff --git a/mnemosyne/mcp_server/auth.py b/mnemosyne/mcp_server/auth.py index 7fdcb8e..3a5ff29 100644 --- a/mnemosyne/mcp_server/auth.py +++ b/mnemosyne/mcp_server/auth.py @@ -207,7 +207,14 @@ class MCPAuthMiddleware(Middleware): async def on_call_tool(self, context: MiddlewareContext, call_next): require_auth = getattr(settings, "MCP_REQUIRE_AUTH", True) - if require_auth and self._extract_tool_name(context) in self._PUBLIC_TOOLS: + tool_name = self._extract_tool_name(context) + logger.info( + "mcp_auth.on_call_tool tool=%s require_auth=%s", + tool_name, + require_auth, + ) + + if require_auth and tool_name in self._PUBLIC_TOOLS: return await call_next(context) token_string = self._extract_token() @@ -257,13 +264,46 @@ class MCPAuthMiddleware(Middleware): @staticmethod def _extract_token() -> str | None: + """Pull the Bearer token off the current HTTP request, if any. + + The MCP SDK stores the Starlette ``Request`` on ``request_ctx`` for + every tool-call dispatch (including follow-up calls on a stateful + session), so ``get_http_request()`` should succeed here. If it + *doesn't* — e.g. because we're in a background task or pre-session + initialize hook — we return ``None`` and let the caller decide. + + INFO-level logging is intentional until bearer-forwarding from + Daedalus/Pallas is fully shaken out; demote to DEBUG once green. + """ try: request = get_http_request() - except RuntimeError: + except RuntimeError as exc: + logger.warning( + "mcp_auth.extract outcome=no_http_request reason=%r", + str(exc), + ) return None + + # Header lookup is case-insensitive on Starlette ``Headers`` but + # different proxies normalize case differently — belt and braces. auth_header = request.headers.get("Authorization", "") + if not auth_header: + auth_header = request.headers.get("authorization", "") + + header_names = sorted(request.headers.keys()) + logger.info( + "mcp_auth.extract outcome=%s len=%d prefix=%r path=%s header_names=%s", + "present" if auth_header else "missing", + len(auth_header), + auth_header[:16] if auth_header else "", + str(getattr(request, "url", "")), + header_names, + ) + if auth_header.startswith("Bearer "): return auth_header[7:].strip() or None + if auth_header.startswith("bearer "): # some clients lowercase + return auth_header[7:].strip() or None return None @staticmethod