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
« prev ^ index » next coverage.py v7.6.12, created at 2025-08-16 18:46 +0200
1"""Platform registration for Golf MCP projects."""
3import os
4from datetime import datetime
5from pathlib import Path
6from typing import Any
8import httpx
9from rich.console import Console
11from golf import __version__
12from golf.core.config import Settings
13from golf.core.parser import ComponentType, ParsedComponent
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
23console = Console()
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.
33 Args:
34 project_path: Path to the project root
35 settings: Project settings
36 components: Parsed components dictionary
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
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
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 }
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()
86 console.print("[green]✓[/green] Registered with Golf platform")
87 return True
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
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.
111 Args:
112 components: Dictionary of parsed components by type
113 project_path: Path to the project root
115 Returns:
116 List of component metadata dictionaries
117 """
118 result = []
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 }
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
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
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
153 elif comp_type == ComponentType.TOOL:
154 if hasattr(comp, "annotations") and comp.annotations:
155 component_data["annotations"] = comp.annotations
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
161 result.append(component_data)
163 return result
166def _get_component_counts(
167 components: dict[ComponentType, list[ParsedComponent]],
168) -> dict[str, int]:
169 """Get component counts by type.
171 Args:
172 components: Dictionary of parsed components by type
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 }