-
Notifications
You must be signed in to change notification settings - Fork 70
LCORE-1246: Add OAuth authentication method for MCP servers #1156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,9 +2,10 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Annotated, Any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Synchronous This module defines 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from fastapi import APIRouter, Depends, HTTPException, Request | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from llama_stack_client import APIConnectionError, BadRequestError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from llama_stack_client import APIConnectionError, BadRequestError, AuthenticationError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from authentication import get_auth_dependency | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from authentication.interface import AuthTuple | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -89,6 +90,18 @@ async def tools_endpoint_handler( # pylint: disable=too-many-locals | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except BadRequestError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error("Toolgroup %s is not found", toolgroup.identifier) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except AuthenticationError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error("Authentication error: %s", e) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if toolgroup.mcp_endpoint: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resp = requests.get(toolgroup.mcp_endpoint.uri, timeout=10) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cause = f"MCP server at {toolgroup.mcp_endpoint.uri} requires OAuth" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_response = UnauthorizedResponse(cause=cause) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **error_response.model_dump(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers={"WWW-Authenticate": resp.headers["WWW-Authenticate"]}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_response = UnauthorizedResponse(cause=str(e)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(**error_response.model_dump()) from e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+93
to
+104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Step 1: Check the function context around lines 93-104 in tools.py
echo "=== Function context around lines 93-104 in src/app/endpoints/tools.py ==="
sed -n '70,110p' src/app/endpoints/tools.py | cat -n
echo ""
echo "=== Check function signature ==="
sed -n '70,95p' src/app/endpoints/tools.py | grep -E "^(async )?def " | head -1Repository: lightspeed-core/lightspeed-stack Length of output: 2401 🏁 Script executed: #!/bin/bash
# Step 2: Check project dependencies for async HTTP clients
echo "=== Check for async HTTP client dependencies ==="
if [ -f "pyproject.toml" ]; then
grep -E "aiohttp|httpx" pyproject.toml
elif [ -f "requirements.txt" ]; then
grep -E "aiohttp|httpx" requirements.txt
else
find . -name "pyproject.toml" -o -name "requirements.txt" -o -name "setup.py" | head -3
fi
echo ""
echo "=== Check imports in tools.py ==="
head -30 src/app/endpoints/tools.py | cat -nRepository: lightspeed-core/lightspeed-stack Length of output: 1347 🏁 Script executed: #!/bin/bash
# Step 3: Search for requests.get usage in src/**/*.py
echo "=== All requests.get() calls in src/ ==="
rg -n "requests\." --type=py src/ | head -20Repository: lightspeed-core/lightspeed-stack Length of output: 1610 🏁 Script executed: #!/bin/bash
# Step 4: Check src/utils/responses.py for get_mcp_tools pattern
echo "=== Check get_mcp_tools function in src/utils/responses.py ==="
rg -n -B2 -A8 "def get_mcp_tools" src/utils/responses.pyRepository: lightspeed-core/lightspeed-stack Length of output: 531 🏁 Script executed: #!/bin/bash
# Check if get_mcp_tools is async and full context around line 398
echo "=== Check if get_mcp_tools is async ==="
sed -n '318,325p' src/utils/responses.py | cat -n
echo ""
echo "=== Full context around line 398 in responses.py ==="
sed -n '390,410p' src/utils/responses.py | cat -n
echo ""
echo "=== Check which function contains line 398 ==="
sed -n '380,400p' src/utils/responses.py | grep -E "^(async )?def " | tail -1Repository: lightspeed-core/lightspeed-stack Length of output: 1712 🏁 Script executed: #!/bin/bash
# Check if there are any await calls or async patterns near line 398
echo "=== Context around requests.get in responses.py with function boundaries ==="
sed -n '1,400p' src/utils/responses.py | tail -100 | head -50Repository: lightspeed-core/lightspeed-stack Length of output: 1901 🏁 Script executed: #!/bin/bash
# Check where get_mcp_tools is called from
echo "=== Where is get_mcp_tools called? ==="
rg -n "get_mcp_tools" src/ --type=py
echo ""
echo "=== Check if those call sites are in async functions ==="
# Check the call in responses.py
rg -n -B5 "get_mcp_tools" src/ --type=py | grep -E "(async def|def )" | head -5Repository: lightspeed-core/lightspeed-stack Length of output: 661 🏁 Script executed: #!/bin/bash
# Verify the full handler function signature in tools.py to confirm it's async
echo "=== Find the full async endpoint function in tools.py ==="
rg -n "^async def\|^@router" src/app/endpoints/tools.py | head -10
echo ""
echo "=== Get the specific endpoint that contains the blocking code ==="
sed -n '60,95p' src/app/endpoints/tools.py | cat -n | tail -20Repository: lightspeed-core/lightspeed-stack Length of output: 1126 🏁 Script executed: #!/bin/bash
# Find the complete function definition that contains the blocking code
echo "=== Find the router decorator and function def in tools.py ==="
sed -n '35,65p' src/app/endpoints/tools.py | cat -n
echo ""
echo "=== Also check for `@router.get` or similar ==="
rg -n "@router" src/app/endpoints/tools.py | head -5Repository: lightspeed-core/lightspeed-stack Length of output: 1539 🏁 Script executed: #!/bin/bash
# Check the call site in responses.py line 188 - is that function async?
echo "=== Check function containing line 188 in responses.py ==="
sed -n '170,195p' src/utils/responses.py | cat -n
echo ""
echo "=== Check the rlsapi_v1.py call site line 268 ==="
sed -n '250,275p' src/app/endpoints/rlsapi_v1.py | cat -nRepository: lightspeed-core/lightspeed-stack Length of output: 2547 Blocking I/O in async handler and unhandled failure modes in the OAuth probe. Three critical issues in this block:
The same pattern exists in Proposed fix (minimal — still uses requests; async migration recommended separately) except AuthenticationError as e:
logger.error("Authentication error: %s", e)
if toolgroup.mcp_endpoint:
- resp = requests.get(toolgroup.mcp_endpoint.uri, timeout=10)
- cause = f"MCP server at {toolgroup.mcp_endpoint.uri} requires OAuth"
- error_response = UnauthorizedResponse(cause=cause)
- raise HTTPException(
- **error_response.model_dump(),
- headers={"WWW-Authenticate": resp.headers["WWW-Authenticate"]},
- ) from e
+ www_authenticate = ""
+ try:
+ resp = requests.get(toolgroup.mcp_endpoint.uri, timeout=10)
+ www_authenticate = resp.headers.get("WWW-Authenticate", "")
+ except requests.RequestException:
+ logger.warning(
+ "Failed to probe MCP server %s for WWW-Authenticate header",
+ toolgroup.mcp_endpoint.uri,
+ )
+ cause = f"MCP server at {toolgroup.mcp_endpoint.uri} requires OAuth"
+ error_response = UnauthorizedResponse(cause=cause)
+ headers = {"WWW-Authenticate": www_authenticate} if www_authenticate else {}
+ raise HTTPException(
+ **error_response.model_dump(),
+ headers=headers,
+ ) from e
error_response = UnauthorizedResponse(cause=str(e))
raise HTTPException(**error_response.model_dump()) from ePer coding guidelines, "Use 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except APIConnectionError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error("Unable to connect to Llama Stack: %s", e) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response = ServiceUnavailableResponse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -505,11 +505,13 @@ class ModelContextProtocolServer(ConfigurationBase): | |||||||||||||
| "Headers to send to the MCP server. " | ||||||||||||||
| "The map contains the header name and the path to a file containing " | ||||||||||||||
| "the header value (secret). " | ||||||||||||||
| "There are 2 special cases: " | ||||||||||||||
| "There are 3 special cases: " | ||||||||||||||
| "1. Usage of the kubernetes token in the header. " | ||||||||||||||
| "To specify this use a string 'kubernetes' instead of the file path. " | ||||||||||||||
| "2. Usage of the client provided token in the header. " | ||||||||||||||
| "To specify this use a string 'client' instead of the file path." | ||||||||||||||
| "3. Usage of the oauth token in the header. " | ||||||||||||||
| "To specify this use a string 'oauth' instead of the file path. " | ||||||||||||||
|
Comment on lines
512
to
+514
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing space between special cases 2 and 3 in the description string. Line 512 ends without a trailing space, so the concatenated text reads Proposed fix- "To specify this use a string 'client' instead of the file path."
- "3. Usage of the oauth token in the header. "
+ "To specify this use a string 'client' instead of the file path. "
+ "3. Usage of the oauth token in the header. "📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| ), | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Any, Optional, cast | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from fastapi import HTTPException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from llama_stack_api.openai_responses import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OpenAIResponseObject, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,6 +29,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from models.responses import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InternalServerErrorResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ServiceUnavailableResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UnauthorizedResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from utils.prompts import get_system_prompt, get_topic_summary_system_prompt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from utils.query import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -313,7 +315,7 @@ def get_rag_tools(vector_store_ids: list[str]) -> Optional[list[dict[str, Any]]] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_mcp_tools( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_mcp_tools( # pylint: disable=too-many-return-statements | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp_servers: list[ModelContextProtocolServer], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token: str | None = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp_headers: Optional[McpHeaders] = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -327,6 +329,10 @@ def get_mcp_tools( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List of MCP tool definitions with server details and optional auth headers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Raises: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HTTPException: 401 with WWW-Authenticate header when an MCP server uses OAuth, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| no headers are passed, and the server responds with 401 and WWW-Authenticate. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _get_token_value(original: str, header: str) -> str | None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -345,6 +351,14 @@ def _get_token_value(original: str, header: str) -> str | None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if c_headers is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return c_headers.get(header, None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case constants.MCP_AUTH_OAUTH: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # use oauth token | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if mcp_headers is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| c_headers = mcp_headers.get(mcp_server.name, None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if c_headers is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return c_headers.get(header, None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case _: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # use provided | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return original | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -372,6 +386,23 @@ def _get_token_value(original: str, header: str) -> str | None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if mcp_server.authorization_headers and len(headers) != len( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp_server.authorization_headers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If OAuth was required and no headers passed, probe endpoint and forward | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 401 with WWW-Authenticate so the client can perform OAuth | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses_oauth = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constants.MCP_AUTH_OAUTH | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| in mcp_server.resolved_authorization_headers.values() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if uses_oauth and ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp_headers is None or not mcp_headers.get(mcp_server.name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resp = requests.get(mcp_server.url, timeout=10) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error_response = UnauthorizedResponse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cause=f"MCP server at {mcp_server.url} requires OAuth authentication", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **error_response.model_dump(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers={"WWW-Authenticate": resp.headers["WWW-Authenticate"]}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+389
to
+405
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing error handling: Several unhandled failure modes:
At minimum, wrap the probe in a try/except and use Proposed fix for error handling if uses_oauth and (
mcp_headers is None or not mcp_headers.get(mcp_server.name)
):
- resp = requests.get(mcp_server.url, timeout=10)
- error_response = UnauthorizedResponse(
- cause=f"MCP server at {mcp_server.url} requires OAuth authentication",
- )
- raise HTTPException(
- **error_response.model_dump(),
- headers={"WWW-Authenticate": resp.headers["WWW-Authenticate"]},
- )
+ error_response = UnauthorizedResponse(
+ cause=f"MCP server at {mcp_server.url} requires OAuth authentication",
+ )
+ headers: dict[str, str] = {}
+ try:
+ resp = requests.get(mcp_server.url, timeout=10)
+ www_auth = resp.headers.get("WWW-Authenticate")
+ if www_auth:
+ headers["WWW-Authenticate"] = www_auth
+ except requests.RequestException:
+ logger.warning(
+ "Failed to probe MCP server %s for WWW-Authenticate header",
+ mcp_server.url,
+ )
+ raise HTTPException(
+ **error_response.model_dump(),
+ headers=headers if headers else None,
+ )As per coding guidelines: Use 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Skipping MCP server %s: required %d auth headers but only resolved %d", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp_server.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor grammar: "client provided" → "client-provided".
Static analysis flagged this. When used as a compound adjective before a noun, it should be hyphenated.
Suggested fix
📝 Committable suggestion
🧰 Tools
🪛 LanguageTool
[grammar] ~374-~374: Use a hyphen to join words.
Context: ...of the file path. 2. Usage of the client provided token in the header — use the s...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents