Coverage for /Users/antonigmitruk/golf/src/golf/core/builder_auth.py: 0%

32 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-08-16 18:46 +0200

1"""Authentication integration for the Golf MCP build process. 

2 

3This module adds support for injecting authentication configuration 

4into the generated FastMCP application during the build process using 

5FastMCP 2.11+ built-in auth providers. 

6""" 

7 

8from golf.auth import get_auth_config, is_auth_configured 

9from golf.auth.api_key import get_api_key_config 

10from golf.auth.providers import AuthConfig 

11 

12 

13def generate_auth_code( 

14 server_name: str, 

15 host: str = "localhost", 

16 port: int = 3000, 

17 https: bool = False, 

18 opentelemetry_enabled: bool = False, 

19 transport: str = "streamable-http", 

20) -> dict: 

21 """Generate authentication components for the FastMCP app using modern 

22 auth providers. 

23 

24 Returns a dictionary with: 

25 - imports: List of import statements 

26 - setup_code: Auth setup code (provider configuration, etc.) 

27 - fastmcp_args: Dict of arguments to add to FastMCP constructor 

28 - has_auth: Whether auth is configured 

29 """ 

30 # Check for API key configuration first 

31 api_key_config = get_api_key_config() 

32 if api_key_config: 

33 return generate_api_key_auth_components(server_name, opentelemetry_enabled, transport) 

34 

35 # Check for modern auth configuration 

36 auth_config = get_auth_config() 

37 if not auth_config: 

38 # If no auth config, return empty components 

39 return {"imports": [], "setup_code": [], "fastmcp_args": {}, "has_auth": False} 

40 

41 # Validate that we have a modern auth config 

42 if not isinstance(auth_config, AuthConfig): 

43 raise ValueError( 

44 f"Invalid auth configuration type: {type(auth_config).__name__}. " 

45 "Golf 0.2.x requires modern auth configurations (JWTAuthConfig, " 

46 "StaticTokenConfig, OAuthServerConfig, or RemoteAuthConfig). " 

47 "Please update your auth.py file." 

48 ) 

49 

50 # Generate modern auth components with embedded configuration 

51 auth_imports = [ 

52 "import os", 

53 "import sys", 

54 "from golf.auth.factory import create_auth_provider", 

55 "from golf.auth.providers import RemoteAuthConfig, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig", 

56 ] 

57 

58 # Embed the auth configuration directly in the generated code 

59 # Convert the auth config to its string representation for embedding 

60 auth_config_repr = repr(auth_config) 

61 

62 setup_code_lines = [ 

63 "# Modern FastMCP 2.11+ authentication setup with embedded configuration", 

64 f"auth_config = {auth_config_repr}", 

65 "try:", 

66 " auth_provider = create_auth_provider(auth_config)", 

67 " print(f'Authentication configured with {auth_config.provider_type} provider')", 

68 "except Exception as e:", 

69 " print(f'Authentication setup failed: {e}', file=sys.stderr)", 

70 " auth_provider = None", 

71 "", 

72 ] 

73 

74 # FastMCP constructor arguments - FastMCP 2.11+ uses auth parameter 

75 fastmcp_args = {"auth": "auth_provider"} 

76 

77 return { 

78 "imports": auth_imports, 

79 "setup_code": setup_code_lines, 

80 "fastmcp_args": fastmcp_args, 

81 "has_auth": True, 

82 } 

83 

84 

85def generate_api_key_auth_components( 

86 server_name: str, 

87 opentelemetry_enabled: bool = False, 

88 transport: str = "streamable-http", 

89) -> dict: 

90 """Generate authentication components for API key authentication. 

91 

92 Returns a dictionary with: 

93 - imports: List of import statements 

94 - setup_code: Auth setup code (middleware setup) 

95 - fastmcp_args: Dict of arguments to add to FastMCP constructor 

96 - has_auth: Whether auth is configured 

97 """ 

98 api_key_config = get_api_key_config() 

99 if not api_key_config: 

100 return {"imports": [], "setup_code": [], "fastmcp_args": {}, "has_auth": False} 

101 

102 auth_imports = [ 

103 "# API key authentication setup", 

104 "from golf.auth.api_key import get_api_key_config, configure_api_key", 

105 "from golf.auth import set_api_key", 

106 "from starlette.middleware.base import BaseHTTPMiddleware", 

107 "from starlette.requests import Request", 

108 "from starlette.responses import JSONResponse", 

109 "import os", 

110 ] 

111 

112 setup_code_lines = [ 

113 "# Recreate API key configuration from auth.py", 

114 "configure_api_key(", 

115 f" header_name={repr(api_key_config.header_name)},", 

116 f" header_prefix={repr(api_key_config.header_prefix)},", 

117 f" required={repr(api_key_config.required)}", 

118 ")", 

119 "", 

120 "# Simplified API key middleware that validates presence", 

121 "class ApiKeyMiddleware(BaseHTTPMiddleware):", 

122 " async def dispatch(self, request: Request, call_next):", 

123 " # Debug mode from environment", 

124 " debug = os.environ.get('GOLF_API_KEY_DEBUG', '').lower() == 'true'", 

125 " ", 

126 " # Skip auth for monitoring endpoints", 

127 " path = request.url.path", 

128 " if path in ['/metrics', '/health']:", 

129 " return await call_next(request)", 

130 " ", 

131 " api_key_config = get_api_key_config()", 

132 " ", 

133 " if api_key_config:", 

134 " # Extract API key from the configured header", 

135 " header_name = api_key_config.header_name", 

136 " header_prefix = api_key_config.header_prefix", 

137 " ", 

138 " # Case-insensitive header lookup", 

139 " api_key = None", 

140 " for k, v in request.headers.items():", 

141 " if k.lower() == header_name.lower():", 

142 " api_key = v", 

143 " break", 

144 " ", 

145 " # Process the API key if found", 

146 " if api_key:", 

147 " # Strip prefix if configured", 

148 " if header_prefix and api_key.startswith(header_prefix):", 

149 " api_key = api_key[len(header_prefix):]", 

150 " ", 

151 " # Store the API key in request state for tools to access", 

152 " request.state.api_key = api_key", 

153 " ", 

154 " # Also store in context variable for tools", 

155 " set_api_key(api_key)", 

156 " ", 

157 " # Check if API key is required but missing", 

158 " if api_key_config.required and not api_key:", 

159 " return JSONResponse(", 

160 " {'error': 'unauthorized', " 

161 "'detail': f'Missing required {header_name} header'}," 

162 " status_code=401,", 

163 " headers={'WWW-Authenticate': f'{header_name} realm=\"MCP Server\"'}", 

164 " )", 

165 " ", 

166 " # Continue with the request", 

167 " return await call_next(request)", 

168 "", 

169 ] 

170 

171 # API key auth is handled via middleware, not FastMCP constructor args 

172 fastmcp_args = {} 

173 

174 return { 

175 "imports": auth_imports, 

176 "setup_code": setup_code_lines, 

177 "fastmcp_args": fastmcp_args, 

178 "has_auth": True, 

179 } 

180 

181 

182def generate_auth_routes() -> str: 

183 """Generate code for auth routes in the FastMCP app. 

184 

185 Auth providers (RemoteAuthProvider, OAuthProvider) provide OAuth metadata routes 

186 that need to be added to the server. 

187 """ 

188 # API key auth doesn't need special routes 

189 api_key_config = get_api_key_config() 

190 if api_key_config: 

191 return "" 

192 

193 # Check if auth is configured 

194 if not is_auth_configured(): 

195 return "" 

196 

197 # Auth providers provide OAuth metadata routes that need to be added to the server 

198 return """ 

199# Add OAuth metadata routes from auth provider 

200if auth_provider and hasattr(auth_provider, 'get_routes'): 

201 auth_routes = auth_provider.get_routes() 

202 if auth_routes: 

203 # Add routes to FastMCP's additional HTTP routes list 

204 try: 

205 mcp._additional_http_routes.extend(auth_routes) 

206 print(f"Added {len(auth_routes)} OAuth metadata routes") 

207 except Exception as e: 

208 print(f"Warning: Failed to add OAuth routes: {e}") 

209"""