golf-mcp 0.2.16__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. golf/__init__.py +1 -0
  2. golf/auth/__init__.py +277 -0
  3. golf/auth/api_key.py +73 -0
  4. golf/auth/factory.py +360 -0
  5. golf/auth/helpers.py +175 -0
  6. golf/auth/providers.py +586 -0
  7. golf/auth/registry.py +256 -0
  8. golf/cli/__init__.py +1 -0
  9. golf/cli/branding.py +191 -0
  10. golf/cli/main.py +377 -0
  11. golf/commands/__init__.py +5 -0
  12. golf/commands/build.py +81 -0
  13. golf/commands/init.py +290 -0
  14. golf/commands/run.py +137 -0
  15. golf/core/__init__.py +1 -0
  16. golf/core/builder.py +1884 -0
  17. golf/core/builder_auth.py +209 -0
  18. golf/core/builder_metrics.py +221 -0
  19. golf/core/builder_telemetry.py +99 -0
  20. golf/core/config.py +199 -0
  21. golf/core/parser.py +1085 -0
  22. golf/core/telemetry.py +492 -0
  23. golf/core/transformer.py +231 -0
  24. golf/examples/__init__.py +0 -0
  25. golf/examples/basic/.env.example +4 -0
  26. golf/examples/basic/README.md +133 -0
  27. golf/examples/basic/auth.py +76 -0
  28. golf/examples/basic/golf.json +5 -0
  29. golf/examples/basic/prompts/welcome.py +27 -0
  30. golf/examples/basic/resources/current_time.py +34 -0
  31. golf/examples/basic/resources/info.py +28 -0
  32. golf/examples/basic/resources/weather/city.py +46 -0
  33. golf/examples/basic/resources/weather/client.py +48 -0
  34. golf/examples/basic/resources/weather/current.py +36 -0
  35. golf/examples/basic/resources/weather/forecast.py +36 -0
  36. golf/examples/basic/tools/calculator.py +94 -0
  37. golf/examples/basic/tools/say/hello.py +65 -0
  38. golf/metrics/__init__.py +10 -0
  39. golf/metrics/collector.py +320 -0
  40. golf/metrics/registry.py +12 -0
  41. golf/telemetry/__init__.py +23 -0
  42. golf/telemetry/instrumentation.py +1402 -0
  43. golf/utilities/__init__.py +12 -0
  44. golf/utilities/context.py +53 -0
  45. golf/utilities/elicitation.py +170 -0
  46. golf/utilities/sampling.py +221 -0
  47. golf_mcp-0.2.16.dist-info/METADATA +262 -0
  48. golf_mcp-0.2.16.dist-info/RECORD +52 -0
  49. golf_mcp-0.2.16.dist-info/WHEEL +5 -0
  50. golf_mcp-0.2.16.dist-info/entry_points.txt +2 -0
  51. golf_mcp-0.2.16.dist-info/licenses/LICENSE +201 -0
  52. golf_mcp-0.2.16.dist-info/top_level.txt +1 -0
golf/core/config.py ADDED
@@ -0,0 +1,199 @@
1
+ """Configuration management for GolfMCP."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel, Field, field_validator
7
+ from pydantic_settings import BaseSettings, SettingsConfigDict
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+
13
+ class AuthConfig(BaseModel):
14
+ """Authentication configuration."""
15
+
16
+ provider: str = Field(..., description="Authentication provider (e.g., 'jwks', 'google', 'github')")
17
+ scopes: list[str] = Field(default_factory=list, description="Required OAuth scopes")
18
+ client_id_env: str | None = Field(None, description="Environment variable name for client ID")
19
+ client_secret_env: str | None = Field(None, description="Environment variable name for client secret")
20
+ redirect_uri: str | None = Field(None, description="OAuth redirect URI (defaults to localhost callback)")
21
+
22
+ @field_validator("provider")
23
+ @classmethod
24
+ def validate_provider(cls, value: str) -> str:
25
+ """Validate the provider value."""
26
+ valid_providers = {"jwks", "google", "github", "custom"}
27
+ if value not in valid_providers and not value.startswith("custom:"):
28
+ raise ValueError(f"Invalid provider '{value}'. Must be one of {valid_providers} or start with 'custom:'")
29
+ return value
30
+
31
+
32
+ class DeployConfig(BaseModel):
33
+ """Deployment configuration."""
34
+
35
+ default: str = Field("vercel", description="Default deployment target")
36
+ options: dict[str, Any] = Field(default_factory=dict, description="Target-specific options")
37
+
38
+
39
+ class Settings(BaseSettings):
40
+ """GolfMCP application settings."""
41
+
42
+ model_config = SettingsConfigDict(
43
+ env_prefix="GOLF_",
44
+ env_file=".env",
45
+ env_file_encoding="utf-8",
46
+ extra="ignore",
47
+ )
48
+
49
+ # Project metadata
50
+ name: str = Field("GolfMCP Project", description="FastMCP instance name")
51
+ description: str | None = Field(None, description="Project description")
52
+
53
+ # Build settings
54
+ output_dir: str = Field("dist", description="Build artifact folder")
55
+
56
+ # Server settings
57
+ host: str = Field("localhost", description="Server host")
58
+ port: int = Field(3000, description="Server port")
59
+ transport: str = Field(
60
+ "streamable-http",
61
+ description="Transport protocol (streamable-http, sse, stdio)",
62
+ )
63
+
64
+ # Auth settings
65
+ auth: str | AuthConfig | None = Field(None, description="Authentication configuration or URI")
66
+
67
+ # Deploy settings
68
+ deploy: DeployConfig = Field(default_factory=DeployConfig, description="Deployment configuration")
69
+
70
+ # Feature flags
71
+ telemetry: bool = Field(True, description="Enable anonymous telemetry")
72
+
73
+ # Project paths
74
+ tools_dir: str = Field("tools", description="Directory containing tools")
75
+ resources_dir: str = Field("resources", description="Directory containing resources")
76
+ prompts_dir: str = Field("prompts", description="Directory containing prompts")
77
+
78
+ # OpenTelemetry config
79
+ opentelemetry_enabled: bool = Field(False, description="Enable OpenTelemetry tracing")
80
+ opentelemetry_default_exporter: str = Field("console", description="Default OpenTelemetry exporter type")
81
+ detailed_tracing: bool = Field(
82
+ False, description="Enable detailed tracing with input/output capture (may contain sensitive data)"
83
+ )
84
+
85
+ # Health check configuration
86
+ health_check_enabled: bool = Field(False, description="Enable health check endpoint (deprecated - use health.py)")
87
+ health_check_path: str = Field("/health", description="Health check endpoint path")
88
+ health_check_response: str = Field("OK", description="Health check response text (deprecated - use health.py)")
89
+
90
+ # HTTP session behaviour
91
+ stateless_http: bool = Field(
92
+ False,
93
+ description="Make Streamable-HTTP transport stateless (new session per request)",
94
+ )
95
+
96
+ # Metrics configuration
97
+ metrics_enabled: bool = Field(False, description="Enable Prometheus metrics endpoint")
98
+ metrics_path: str = Field("/metrics", description="Metrics endpoint path")
99
+
100
+
101
+ def find_config_path(start_path: Path | None = None) -> Path | None:
102
+ """Find the golf config file by searching upwards from the given path.
103
+
104
+ Args:
105
+ start_path: Path to start searching from (defaults to current directory)
106
+
107
+ Returns:
108
+ Path to the config file if found, None otherwise
109
+ """
110
+ if start_path is None:
111
+ start_path = Path.cwd()
112
+
113
+ current = start_path.absolute()
114
+
115
+ # Don't search above the home directory
116
+ home = Path.home().absolute()
117
+
118
+ while current != current.parent and current != home:
119
+ # Check for JSON config first (preferred)
120
+ json_config = current / "golf.json"
121
+ if json_config.exists():
122
+ return json_config
123
+
124
+ # Fall back to TOML config
125
+ toml_config = current / "golf.toml"
126
+ if toml_config.exists():
127
+ return toml_config
128
+
129
+ current = current.parent
130
+
131
+ return None
132
+
133
+
134
+ def find_project_root(
135
+ start_path: Path | None = None,
136
+ ) -> tuple[Path | None, Path | None]:
137
+ """Find a GolfMCP project root by searching for a config file.
138
+
139
+ This is the central project discovery function that should be used by all commands.
140
+
141
+ Args:
142
+ start_path: Path to start searching from (defaults to current directory)
143
+
144
+ Returns:
145
+ Tuple of (project_root, config_path) if a project is found, or
146
+ (None, None) if not
147
+ """
148
+ config_path = find_config_path(start_path)
149
+ if config_path:
150
+ return config_path.parent, config_path
151
+ return None, None
152
+
153
+
154
+ def load_settings(project_path: str | Path) -> Settings:
155
+ """Load settings from a project directory.
156
+
157
+ Args:
158
+ project_path: Path to the project directory
159
+
160
+ Returns:
161
+ Settings object with values loaded from config files
162
+ """
163
+ # Convert to Path if needed
164
+ if isinstance(project_path, str):
165
+ project_path = Path(project_path)
166
+
167
+ # Create default settings
168
+ settings = Settings()
169
+
170
+ # Check for .env file
171
+ env_file = project_path / ".env"
172
+ if env_file.exists():
173
+ settings = Settings(_env_file=env_file)
174
+
175
+ # Try to load JSON config file first
176
+ json_config_path = project_path / "golf.json"
177
+ if json_config_path.exists():
178
+ return _load_json_settings(json_config_path, settings)
179
+
180
+ return settings
181
+
182
+
183
+ def _load_json_settings(path: Path, settings: Settings) -> Settings:
184
+ """Load settings from a JSON file."""
185
+ try:
186
+ import json
187
+
188
+ with open(path) as f:
189
+ config_data = json.load(f)
190
+
191
+ # Update settings from config data
192
+ for key, value in config_data.items():
193
+ if hasattr(settings, key):
194
+ setattr(settings, key, value)
195
+
196
+ return settings
197
+ except Exception as e:
198
+ console.print(f"[bold red]Error loading JSON config from {path}: {e}[/bold red]")
199
+ return settings