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

70 statements  

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

1"""Platform registration for Golf MCP projects.""" 

2 

3import os 

4from datetime import datetime 

5from pathlib import Path 

6from typing import Any 

7 

8import httpx 

9from rich.console import Console 

10 

11from golf import __version__ 

12from golf.core.config import Settings 

13from golf.core.parser import ComponentType, ParsedComponent 

14 

15# Import endpoints with fallback for dev mode 

16try: 

17 # In built wheels, this exists (generated from _endpoints.py.in) 

18 from golf import _endpoints # type: ignore 

19except ImportError: 

20 # In editable/dev installs, fall back to env-based values 

21 from golf import _endpoints_fallback as _endpoints # type: ignore 

22 

23console = Console() 

24 

25 

26async def register_project_with_platform( 

27 project_path: Path, 

28 settings: Settings, 

29 components: dict[ComponentType, list[ParsedComponent]], 

30) -> bool: 

31 """Register project with Golf platform during prod build. 

32 

33 Args: 

34 project_path: Path to the project root 

35 settings: Project settings 

36 components: Parsed components dictionary 

37 

38 Returns: 

39 True if registration succeeded or was skipped, False if failed 

40 """ 

41 # Check if platform integration is enabled 

42 api_key = os.getenv("GOLF_API_KEY") 

43 if not api_key: 

44 return True # Skip silently if no API key 

45 

46 # Require explicit server ID 

47 server_id = os.getenv("GOLF_SERVER_ID") 

48 if not server_id: 

49 console.print( 

50 "[yellow]Warning: Platform registration skipped - GOLF_SERVER_ID environment variable required[/yellow]" 

51 ) 

52 return True # Skip registration but don't fail build 

53 

54 # Build metadata payload 

55 metadata = { 

56 "project_name": settings.name, 

57 "description": settings.description, 

58 "server_id": server_id, 

59 "components": _build_component_list(components, project_path), 

60 "build_timestamp": datetime.utcnow().isoformat(), 

61 "golf_version": __version__, 

62 "component_counts": _get_component_counts(components), 

63 "server_config": { 

64 "host": settings.host, 

65 "port": settings.port, 

66 "transport": settings.transport, 

67 "auth_enabled": bool(settings.auth), 

68 "telemetry_enabled": settings.opentelemetry_enabled, 

69 "health_check_enabled": settings.health_check_enabled, 

70 }, 

71 } 

72 

73 try: 

74 async with httpx.AsyncClient(timeout=10.0) as client: 

75 response = await client.post( 

76 _endpoints.PLATFORM_API_URL, 

77 json=metadata, 

78 headers={ 

79 "X-Golf-Key": api_key, 

80 "Content-Type": "application/json", 

81 "User-Agent": f"Golf-MCP/{__version__}", 

82 }, 

83 ) 

84 response.raise_for_status() 

85 

86 console.print("[green]✓[/green] Registered with Golf platform") 

87 return True 

88 

89 except httpx.TimeoutException: 

90 console.print("[yellow]Warning: Platform registration timed out[/yellow]") 

91 return False 

92 except httpx.HTTPStatusError as e: 

93 if e.response.status_code == 401: 

94 console.print("[yellow]Warning: Platform registration failed - invalid API key[/yellow]") 

95 elif e.response.status_code == 403: 

96 console.print("[yellow]Warning: Platform registration failed - access denied[/yellow]") 

97 else: 

98 console.print(f"[yellow]Warning: Platform registration failed - HTTP {e.response.status_code}[/yellow]") 

99 return False 

100 except Exception as e: 

101 console.print(f"[yellow]Warning: Platform registration failed: {e}[/yellow]") 

102 return False # Don't fail the build 

103 

104 

105def _build_component_list( 

106 components: dict[ComponentType, list[ParsedComponent]], 

107 project_path: Path, 

108) -> list[dict[str, Any]]: 

109 """Convert parsed components to platform format. 

110 

111 Args: 

112 components: Dictionary of parsed components by type 

113 project_path: Path to the project root 

114 

115 Returns: 

116 List of component metadata dictionaries 

117 """ 

118 result = [] 

119 

120 for comp_type, comp_list in components.items(): 

121 for comp in comp_list: 

122 # Start with basic component data 

123 component_data = { 

124 "name": comp.name, 

125 "type": comp_type.value, 

126 "description": comp.docstring, 

127 "entry_function": comp.entry_function, 

128 "parent_module": comp.parent_module, 

129 } 

130 

131 # Add file path relative to project root if available 

132 if comp.file_path: 

133 try: 

134 file_path = Path(comp.file_path) 

135 # Use the provided project_path for relative calculation 

136 rel_path = file_path.relative_to(project_path) 

137 component_data["file_path"] = str(rel_path) 

138 except ValueError: 

139 # If relative_to fails, try to find a common path or use filename 

140 component_data["file_path"] = Path(comp.file_path).name 

141 

142 # Add schema information only if available and not None 

143 if hasattr(comp, "input_schema") and comp.input_schema: 

144 component_data["input_schema"] = comp.input_schema 

145 if hasattr(comp, "output_schema") and comp.output_schema: 

146 component_data["output_schema"] = comp.output_schema 

147 

148 # Add component-specific fields only if they have values 

149 if comp_type == ComponentType.RESOURCE: 

150 if hasattr(comp, "uri_template") and comp.uri_template: 

151 component_data["uri_template"] = comp.uri_template 

152 

153 elif comp_type == ComponentType.TOOL: 

154 if hasattr(comp, "annotations") and comp.annotations: 

155 component_data["annotations"] = comp.annotations 

156 

157 # Add parameters only if they exist and are not empty 

158 if hasattr(comp, "parameters") and comp.parameters: 

159 component_data["parameters"] = comp.parameters 

160 

161 result.append(component_data) 

162 

163 return result 

164 

165 

166def _get_component_counts( 

167 components: dict[ComponentType, list[ParsedComponent]], 

168) -> dict[str, int]: 

169 """Get component counts by type. 

170 

171 Args: 

172 components: Dictionary of parsed components by type 

173 

174 Returns: 

175 Dictionary with counts for each component type 

176 """ 

177 return { 

178 "tools": len(components.get(ComponentType.TOOL, [])), 

179 "resources": len(components.get(ComponentType.RESOURCE, [])), 

180 "prompts": len(components.get(ComponentType.PROMPT, [])), 

181 "total": sum(len(comp_list) for comp_list in components.values()), 

182 }