11import logging
2+ import os
23import re
34from urllib .parse import urljoin as urllib_urljoin
45
6+ from fastmcp .server .dependencies import get_http_headers
7+ from mcp .server .fastmcp .exceptions import ValidationError
8+
59from .client import GitGuardianClient
610
711# Setup logger
@@ -27,25 +31,100 @@ def get_client(personal_access_token: str | None = None) -> GitGuardianClient:
2731 with that token (not cached). This is useful for per-request authentication
2832 via HTTP Authorization headers.
2933
34+ In HTTP/SSE mode (when MCP_PORT is set), this function automatically extracts
35+ the token from the Authorization header of the current request.
36+
3037 Args:
3138 personal_access_token: Optional Personal Access Token to use for authentication.
3239 If provided, a new client instance is created with this token.
3340
3441 Returns:
3542 GitGuardianClient: The cached client instance or a new instance with the provided PAT
3643 """
37- # If a PAT is provided, create a new client instance (don't use singleton)
44+ # Check if we're in HTTP/SSE mode (MCP_PORT is set)
45+ mcp_port = os .environ .get ("MCP_PORT" )
46+
47+ logger .debug (
48+ f"get_client() called: mcp_port={ mcp_port } , personal_access_token={ 'provided' if personal_access_token else 'None' } "
49+ )
50+
51+ if mcp_port and not personal_access_token :
52+ # In HTTP mode, get token from Authorization header or raise
53+ logger .debug ("HTTP mode detected, extracting token from request headers" )
54+ try :
55+ personal_access_token = get_personal_access_token_from_request ()
56+ logger .info ("Successfully extracted token from HTTP request headers" )
57+ except ValidationError as e :
58+ logger .error (f"Failed to extract token from HTTP headers: { e } " )
59+ raise
60+
61+ # If a PAT is provided (or extracted from headers), create a new client instance (don't use singleton)
3862 if personal_access_token :
3963 logger .debug ("Creating new GitGuardian client with provided Personal Access Token" )
4064 return get_gitguardian_client (personal_access_token = personal_access_token )
4165
4266 # Otherwise, use the singleton pattern
67+ logger .debug ("Using singleton client (no PAT provided)" )
4368 global _client_singleton
4469 if _client_singleton is None :
70+ logger .info ("Creating singleton client instance" )
4571 _client_singleton = get_gitguardian_client ()
4672 return _client_singleton
4773
4874
75+ def get_personal_access_token_from_request ():
76+ """Extract personal access token from HTTP request headers.
77+
78+ Raises:
79+ ValidationError: If headers are missing or invalid
80+ """
81+ try :
82+ headers = get_http_headers ()
83+ logger .debug (f"Retrieved HTTP headers: { list (headers .keys ()) if headers else 'None' } " )
84+ except Exception as e :
85+ logger .error (f"Failed to get HTTP headers: { e } " )
86+ raise ValidationError (f"Failed to retrieve HTTP headers: { e } " )
87+
88+ if not headers :
89+ logger .error ("No HTTP headers available in current context" )
90+ raise ValidationError ("No HTTP headers available - Authorization header required in HTTP mode" )
91+
92+ auth_header = headers .get ("authorization" ) or headers .get ("Authorization" )
93+ if not auth_header :
94+ logger .error (f"Missing Authorization header. Available headers: { list (headers .keys ())} " )
95+ raise ValidationError ("Missing Authorization header - required in HTTP mode" )
96+
97+ token = _extract_token_from_auth_header (auth_header )
98+ if not token :
99+ logger .error ("Failed to extract token from Authorization header" )
100+ raise ValidationError ("Invalid Authorization header format" )
101+
102+ logger .debug ("Successfully extracted token from Authorization header" )
103+ return token
104+
105+
106+ def _extract_token_from_auth_header (auth_header : str ) -> str | None :
107+ """Extract token from Authorization header.
108+
109+ Supports formats:
110+ - Bearer <token>
111+ - Token <token>
112+ - <token> (raw)
113+ """
114+ auth_header = auth_header .strip ()
115+
116+ if auth_header .lower ().startswith ("bearer " ):
117+ return auth_header [7 :].strip ()
118+
119+ if auth_header .lower ().startswith ("token " ):
120+ return auth_header [6 :].strip ()
121+
122+ if auth_header :
123+ return auth_header
124+
125+ return None
126+
127+
49128def parse_repo_url (remote_url : str ) -> str | None :
50129 """Parse repository name from git remote URL.
51130
0 commit comments