golf-mcp 0.1.2__py3-none-any.whl → 0.1.3__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.

Potentially problematic release.


This version of golf-mcp might be problematic. Click here for more details.

golf/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.2"
1
+ __version__ = "0.1.3"
golf/cli/main.py CHANGED
@@ -3,6 +3,7 @@
3
3
  import os
4
4
  from pathlib import Path
5
5
  from typing import Optional
6
+ import atexit
6
7
 
7
8
  import typer
8
9
  from rich.console import Console
@@ -10,6 +11,7 @@ from rich.panel import Panel
10
11
 
11
12
  from golf import __version__
12
13
  from golf.core.config import load_settings, find_project_root
14
+ from golf.core.telemetry import is_telemetry_enabled, shutdown, set_telemetry_enabled, track_event
13
15
 
14
16
  # Create console for rich output
15
17
  console = Console()
@@ -21,6 +23,9 @@ app = typer.Typer(
21
23
  add_completion=False,
22
24
  )
23
25
 
26
+ # Register telemetry shutdown on exit
27
+ atexit.register(shutdown)
28
+
24
29
 
25
30
  def _version_callback(value: bool) -> None:
26
31
  """Print version and exit if --version flag is used."""
@@ -43,7 +48,7 @@ def callback(
43
48
  False, "--verbose", "-v", help="Increase verbosity of output."
44
49
  ),
45
50
  no_telemetry: bool = typer.Option(
46
- False, "--no-telemetry", help="Disable telemetry collection."
51
+ False, "--no-telemetry", help="Disable telemetry collection (persists for future commands)."
47
52
  ),
48
53
  ) -> None:
49
54
  """GolfMCP: A Pythonic framework for building MCP servers with zero boilerplate."""
@@ -51,9 +56,10 @@ def callback(
51
56
  if verbose:
52
57
  os.environ["GOLF_VERBOSE"] = "1"
53
58
 
54
- # Set telemetry flag
59
+ # Set telemetry preference if flag is used
55
60
  if no_telemetry:
56
- os.environ["GOLF_TELEMETRY"] = "0"
61
+ set_telemetry_enabled(False, persist=True)
62
+ console.print("[dim]Telemetry has been disabled. You can re-enable it with: golf telemetry enable[/dim]")
57
63
 
58
64
 
59
65
  @app.command()
@@ -78,7 +84,7 @@ def init(
78
84
  if output_dir is None:
79
85
  output_dir = Path.cwd() / project_name
80
86
 
81
- # Execute the initialization command
87
+ # Execute the initialization command (it handles its own tracking)
82
88
  initialize_project(project_name=project_name, output_dir=output_dir, template=template)
83
89
 
84
90
 
@@ -100,6 +106,7 @@ def build_dev(
100
106
  if not project_root:
101
107
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
102
108
  console.print("Run 'golf init <project_name>' to create a new project.")
109
+ track_event("cli_build_failed", {"success": False, "environment": "dev"})
103
110
  raise typer.Exit(code=1)
104
111
 
105
112
  # Load settings from the found project
@@ -111,9 +118,15 @@ def build_dev(
111
118
  else:
112
119
  output_dir = Path(output_dir)
113
120
 
114
- # Build the project with environment variables copied
115
- from golf.commands.build import build_project
116
- build_project(project_root, settings, output_dir, build_env="dev", copy_env=True)
121
+ try:
122
+ # Build the project with environment variables copied
123
+ from golf.commands.build import build_project
124
+ build_project(project_root, settings, output_dir, build_env="dev", copy_env=True)
125
+ # Track successful build with environment
126
+ track_event("cli_build_success", {"success": True, "environment": "dev"})
127
+ except Exception:
128
+ track_event("cli_build_failed", {"success": False, "environment": "dev"})
129
+ raise
117
130
 
118
131
 
119
132
  @build_app.command("prod")
@@ -129,6 +142,7 @@ def build_prod(
129
142
  if not project_root:
130
143
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
131
144
  console.print("Run 'golf init <project_name>' to create a new project.")
145
+ track_event("cli_build_failed", {"success": False, "environment": "prod"})
132
146
  raise typer.Exit(code=1)
133
147
 
134
148
  # Load settings from the found project
@@ -140,9 +154,15 @@ def build_prod(
140
154
  else:
141
155
  output_dir = Path(output_dir)
142
156
 
143
- # Build the project without copying environment variables
144
- from golf.commands.build import build_project
145
- build_project(project_root, settings, output_dir, build_env="prod", copy_env=False)
157
+ try:
158
+ # Build the project without copying environment variables
159
+ from golf.commands.build import build_project
160
+ build_project(project_root, settings, output_dir, build_env="prod", copy_env=False)
161
+ # Track successful build with environment
162
+ track_event("cli_build_success", {"success": True, "environment": "prod"})
163
+ except Exception:
164
+ track_event("cli_build_failed", {"success": False, "environment": "prod"})
165
+ raise
146
166
 
147
167
 
148
168
  @app.command()
@@ -171,6 +191,7 @@ def run(
171
191
  if not project_root:
172
192
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
173
193
  console.print("Run 'golf init <project_name>' to create a new project.")
194
+ track_event("cli_run_failed", {"success": False})
174
195
  raise typer.Exit(code=1)
175
196
 
176
197
  # Load settings from the found project
@@ -192,21 +213,49 @@ def run(
192
213
  else:
193
214
  console.print(f"[bold red]Error: Dist directory {dist_dir} not found.[/bold red]")
194
215
  console.print("Run 'golf build' first or use --build to build automatically.")
216
+ track_event("cli_run_failed", {"success": False})
195
217
  raise typer.Exit(code=1)
196
218
 
197
- # Import and run the server
198
- from golf.commands.run import run_server
199
- return_code = run_server(
200
- project_path=project_root,
201
- settings=settings,
202
- dist_dir=dist_dir,
203
- host=host,
204
- port=port
205
- )
206
-
207
- # Exit with the same code as the server
208
- if return_code != 0:
209
- raise typer.Exit(code=return_code)
219
+ try:
220
+ # Import and run the server
221
+ from golf.commands.run import run_server
222
+ return_code = run_server(
223
+ project_path=project_root,
224
+ settings=settings,
225
+ dist_dir=dist_dir,
226
+ host=host,
227
+ port=port
228
+ )
229
+
230
+ # Track based on return code
231
+ if return_code == 0:
232
+ track_event("cli_run_success", {"success": True})
233
+ else:
234
+ track_event("cli_run_failed", {"success": False})
235
+
236
+ # Exit with the same code as the server
237
+ if return_code != 0:
238
+ raise typer.Exit(code=return_code)
239
+ except Exception:
240
+ track_event("cli_run_failed", {"success": False})
241
+ raise
242
+
243
+
244
+ # Add telemetry command group
245
+ @app.command()
246
+ def telemetry(
247
+ action: str = typer.Argument(..., help="Action to perform: 'enable' or 'disable'")
248
+ ):
249
+ """Manage telemetry settings."""
250
+ if action.lower() == "enable":
251
+ set_telemetry_enabled(True, persist=True)
252
+ console.print("[green]✓[/green] Telemetry enabled. Thank you for helping improve Golf!")
253
+ elif action.lower() == "disable":
254
+ set_telemetry_enabled(False, persist=True)
255
+ console.print("[yellow]Telemetry disabled.[/yellow] You can re-enable it anytime with: golf telemetry enable")
256
+ else:
257
+ console.print(f"[red]Unknown action '{action}'. Use 'enable' or 'disable'.[/red]")
258
+ raise typer.Exit(code=1)
210
259
 
211
260
 
212
261
  if __name__ == "__main__":
@@ -219,5 +268,12 @@ if __name__ == "__main__":
219
268
  )
220
269
  )
221
270
 
271
+ # Add telemetry notice if enabled
272
+ if is_telemetry_enabled():
273
+ console.print(
274
+ "[dim]📊 Anonymous usage data is collected to improve Golf. "
275
+ "Disable with: golf telemetry disable[/dim]\n"
276
+ )
277
+
222
278
  # Run the CLI app
223
279
  app()
golf/commands/init.py CHANGED
@@ -1,12 +1,16 @@
1
1
  """Project initialization command implementation."""
2
2
 
3
+ import os
3
4
  import shutil
4
5
  from pathlib import Path
6
+ from typing import Optional
5
7
 
6
8
  from rich.console import Console
7
9
  from rich.progress import Progress, SpinnerColumn, TextColumn
8
10
  from rich.prompt import Confirm
9
11
 
12
+ from golf.core.telemetry import track_event
13
+
10
14
  console = Console()
11
15
 
12
16
 
@@ -26,6 +30,7 @@ def initialize_project(
26
30
  if template not in ("basic", "advanced"):
27
31
  console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
28
32
  console.print("Available templates: basic, advanced")
33
+ track_event("cli_init_failed", {"success": False})
29
34
  return
30
35
 
31
36
  # Check if directory exists
@@ -34,6 +39,7 @@ def initialize_project(
34
39
  console.print(
35
40
  f"[bold red]Error:[/bold red] '{output_dir}' exists but is not a directory."
36
41
  )
42
+ track_event("cli_init_failed", {"success": False})
37
43
  return
38
44
 
39
45
  # Check if directory is empty
@@ -43,6 +49,7 @@ def initialize_project(
43
49
  default=False,
44
50
  ):
45
51
  console.print("Initialization cancelled.")
52
+ track_event("cli_init_cancelled", {"success": False})
46
53
  return
47
54
  else:
48
55
  # Create the directory
@@ -59,6 +66,7 @@ def initialize_project(
59
66
  console.print(
60
67
  f"[bold red]Error:[/bold red] Could not find template '{template}'"
61
68
  )
69
+ track_event("cli_init_failed", {"success": False})
62
70
  return
63
71
 
64
72
  # Copy template files
@@ -77,6 +85,12 @@ def initialize_project(
77
85
  console.print(f"\nTo get started, run:")
78
86
  console.print(f" cd {output_dir.name}")
79
87
  console.print(f" golf build dev")
88
+
89
+ # Track successful initialization
90
+ track_event("cli_init_success", {
91
+ "success": True,
92
+ "template": template
93
+ })
80
94
 
81
95
 
82
96
  def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> None:
golf/commands/run.py CHANGED
@@ -62,7 +62,11 @@ def run_server(
62
62
  cwd=dist_dir,
63
63
  env=env,
64
64
  )
65
+
65
66
  return process.returncode
66
67
  except KeyboardInterrupt:
67
68
  console.print("\n[yellow]Server stopped by user[/yellow]")
68
- return 0
69
+ return 0
70
+ except Exception as e:
71
+ console.print(f"\n[bold red]Error running server:[/bold red] {e}")
72
+ return 1
golf/core/config.py CHANGED
@@ -83,7 +83,6 @@ class Settings(BaseSettings):
83
83
 
84
84
  # Feature flags
85
85
  telemetry: bool = Field(True, description="Enable anonymous telemetry")
86
- hot_reload: bool = Field(True, description="Enable hot reload in dev mode")
87
86
 
88
87
  # Project paths
89
88
  tools_dir: str = Field("tools", description="Directory containing tools")
golf/core/parser.py CHANGED
@@ -6,7 +6,6 @@ from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  from pathlib import Path
8
8
  from typing import Any, Dict, List, Optional
9
- import hashlib
10
9
 
11
10
  from rich.console import Console
12
11
 
@@ -436,47 +435,7 @@ def parse_project(project_path: Path) -> Dict[ComponentType, List[ParsedComponen
436
435
  return components
437
436
 
438
437
 
439
- def parse_project_incremental(project_path: Path, fingerprints: Dict[str, str]) -> Dict[ComponentType, List[ParsedComponent]]:
440
- """Parse a project with incremental file checking."""
441
- parser = AstParser(project_root=project_path)
442
-
443
- components: Dict[ComponentType, List[ParsedComponent]] = {
444
- ComponentType.TOOL: [],
445
- ComponentType.RESOURCE: [],
446
- ComponentType.PROMPT: []
447
- }
448
-
449
- # Process each directory
450
- for comp_type, dir_name in [
451
- (ComponentType.TOOL, "tools"),
452
- (ComponentType.RESOURCE, "resources"),
453
- (ComponentType.PROMPT, "prompts")
454
- ]:
455
- dir_path = project_path / dir_name
456
- if not dir_path.exists() or not dir_path.is_dir():
457
- continue
458
-
459
- # Parse only changed files using fingerprints
460
- for file_path in dir_path.glob("**/*.py"):
461
- if "__pycache__" in file_path.parts or file_path.name == "__init__.py":
462
- continue
463
-
464
- # Check if file changed
465
- with open(file_path, "rb") as f:
466
- content = f.read()
467
- file_hash = hashlib.sha1(content).hexdigest()
468
-
469
- rel_path = str(file_path.relative_to(project_path))
470
-
471
- if rel_path not in fingerprints or fingerprints[rel_path] != file_hash:
472
- try:
473
- file_components = parser.parse_file(file_path)
474
- components[comp_type].extend([c for c in file_components if c.type == comp_type])
475
- fingerprints[rel_path] = file_hash
476
- except Exception as e:
477
- console.print(f"[bold red]Error parsing {rel_path}: {e}[/bold red]")
478
-
479
- return components
438
+
480
439
 
481
440
 
482
441
  def parse_common_files(project_path: Path) -> Dict[str, Path]:
golf/core/telemetry.py ADDED
@@ -0,0 +1,260 @@
1
+ """Telemetry module for anonymous usage tracking with PostHog."""
2
+
3
+ import os
4
+ import hashlib
5
+ import platform
6
+ from pathlib import Path
7
+ from typing import Optional, Dict, Any
8
+ import json
9
+
10
+ import posthog
11
+ from rich.console import Console
12
+ from dotenv import load_dotenv
13
+
14
+ from golf import __version__
15
+
16
+ console = Console()
17
+
18
+ # Load environment variables from core/.env if it exists
19
+ core_env_path = Path(__file__).parent / ".env"
20
+ if core_env_path.exists():
21
+ load_dotenv(core_env_path)
22
+
23
+ # PostHog configuration
24
+ POSTHOG_API_KEY = os.environ.get("GOLF_POSTHOG_API_KEY", "phc_YOUR_PROJECT_API_KEY")
25
+ POSTHOG_HOST = "https://us.i.posthog.com"
26
+
27
+ # Telemetry state
28
+ _telemetry_enabled: Optional[bool] = None
29
+ _anonymous_id: Optional[str] = None
30
+
31
+
32
+ def get_telemetry_config_path() -> Path:
33
+ """Get the path to the telemetry configuration file."""
34
+ return Path.home() / ".golf" / "telemetry.json"
35
+
36
+
37
+ def save_telemetry_preference(enabled: bool) -> None:
38
+ """Save telemetry preference to persistent storage."""
39
+ config_path = get_telemetry_config_path()
40
+ config_path.parent.mkdir(parents=True, exist_ok=True)
41
+
42
+ config = {
43
+ "enabled": enabled,
44
+ "version": 1
45
+ }
46
+
47
+ try:
48
+ with open(config_path, "w") as f:
49
+ json.dump(config, f)
50
+ except Exception:
51
+ # Don't fail if we can't save the preference
52
+ pass
53
+
54
+
55
+ def load_telemetry_preference() -> Optional[bool]:
56
+ """Load telemetry preference from persistent storage."""
57
+ config_path = get_telemetry_config_path()
58
+
59
+ if not config_path.exists():
60
+ return None
61
+
62
+ try:
63
+ with open(config_path, "r") as f:
64
+ config = json.load(f)
65
+ return config.get("enabled")
66
+ except Exception:
67
+ return None
68
+
69
+
70
+ def is_telemetry_enabled() -> bool:
71
+ """Check if telemetry is enabled.
72
+
73
+ Checks in order:
74
+ 1. Cached value
75
+ 2. GOLF_TELEMETRY environment variable
76
+ 3. Persistent preference file
77
+ 4. Default to True (opt-out model)
78
+ """
79
+ global _telemetry_enabled
80
+
81
+ if _telemetry_enabled is not None:
82
+ return _telemetry_enabled
83
+
84
+ # Check environment variables (highest priority)
85
+ env_telemetry = os.environ.get("GOLF_TELEMETRY", "").lower()
86
+ if env_telemetry in ("0", "false", "no", "off"):
87
+ _telemetry_enabled = False
88
+ return False
89
+ elif env_telemetry in ("1", "true", "yes", "on"):
90
+ _telemetry_enabled = True
91
+ return True
92
+
93
+ # Check persistent preference
94
+ saved_preference = load_telemetry_preference()
95
+ if saved_preference is not None:
96
+ _telemetry_enabled = saved_preference
97
+ return saved_preference
98
+
99
+ # Default to enabled (opt-out model)
100
+ _telemetry_enabled = True
101
+ return True
102
+
103
+
104
+ def set_telemetry_enabled(enabled: bool, persist: bool = True) -> None:
105
+ """Set telemetry enabled state.
106
+
107
+ Args:
108
+ enabled: Whether telemetry should be enabled
109
+ persist: Whether to save this preference persistently
110
+ """
111
+ global _telemetry_enabled
112
+ _telemetry_enabled = enabled
113
+
114
+ if persist:
115
+ save_telemetry_preference(enabled)
116
+
117
+
118
+ def get_anonymous_id() -> str:
119
+ """Get or create a persistent anonymous ID for this machine.
120
+
121
+ The ID is stored in the user's home directory and is based on
122
+ machine characteristics to be consistent across sessions.
123
+ """
124
+ global _anonymous_id
125
+
126
+ if _anonymous_id:
127
+ return _anonymous_id
128
+
129
+ # Try to load existing ID
130
+ id_file = Path.home() / ".golf" / "telemetry_id"
131
+
132
+ if id_file.exists():
133
+ try:
134
+ _anonymous_id = id_file.read_text().strip()
135
+ if _anonymous_id:
136
+ return _anonymous_id
137
+ except Exception:
138
+ pass
139
+
140
+ # Generate new ID based on machine characteristics
141
+ # This ensures the same ID across sessions on the same machine
142
+ machine_data = f"{platform.node()}-{platform.machine()}-{platform.system()}"
143
+ machine_hash = hashlib.sha256(machine_data.encode()).hexdigest()[:16]
144
+ _anonymous_id = f"golf-{machine_hash}"
145
+
146
+ # Try to save for next time
147
+ try:
148
+ id_file.parent.mkdir(parents=True, exist_ok=True)
149
+ id_file.write_text(_anonymous_id)
150
+ except Exception:
151
+ # Not critical if we can't save
152
+ pass
153
+
154
+ return _anonymous_id
155
+
156
+
157
+ def initialize_telemetry() -> None:
158
+ """Initialize PostHog telemetry if enabled."""
159
+ if not is_telemetry_enabled():
160
+ return
161
+
162
+ # Skip initialization if no valid API key
163
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY == "phc_YOUR_PROJECT_API_KEY":
164
+ return
165
+
166
+ try:
167
+ posthog.project_api_key = POSTHOG_API_KEY
168
+ posthog.host = POSTHOG_HOST
169
+
170
+ # Disable PostHog's own logging to avoid noise
171
+ posthog.disabled = False
172
+ posthog.debug = False
173
+
174
+ except Exception:
175
+ # Telemetry should never break the application
176
+ pass
177
+
178
+
179
+ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) -> None:
180
+ """Track an anonymous event with minimal data.
181
+
182
+ Args:
183
+ event_name: Name of the event (e.g., "cli_init", "cli_build")
184
+ properties: Optional properties to include with the event
185
+ """
186
+ if not is_telemetry_enabled():
187
+ return
188
+
189
+ # Skip if no valid API key
190
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY == "phc_YOUR_PROJECT_API_KEY":
191
+ return
192
+
193
+ try:
194
+ # Initialize if needed
195
+ if posthog.project_api_key != POSTHOG_API_KEY:
196
+ initialize_telemetry()
197
+
198
+ # Get anonymous ID
199
+ anonymous_id = get_anonymous_id()
200
+
201
+ # Only include minimal, non-identifying properties
202
+ safe_properties = {
203
+ "golf_version": __version__,
204
+ "python_version": f"{platform.python_version_tuple()[0]}.{platform.python_version_tuple()[1]}",
205
+ "os": platform.system(),
206
+ }
207
+
208
+ # Filter properties to only include safe ones
209
+ if properties:
210
+ # Only include specific safe properties
211
+ safe_keys = {"success", "environment", "template", "command_type"}
212
+ for key in safe_keys:
213
+ if key in properties:
214
+ safe_properties[key] = properties[key]
215
+
216
+ # Send event
217
+ posthog.capture(
218
+ distinct_id=anonymous_id,
219
+ event=event_name,
220
+ properties=safe_properties,
221
+ )
222
+
223
+ except Exception:
224
+ # Telemetry should never break the application
225
+ pass
226
+
227
+
228
+ def track_command(command: str, success: bool = True) -> None:
229
+ """Track a CLI command execution with minimal info.
230
+
231
+ Args:
232
+ command: The command being executed (e.g., "init", "build", "run")
233
+ success: Whether the command was successful
234
+ """
235
+ # Simplify the event to just command and success
236
+ track_event(f"cli_{command}", {"success": success})
237
+
238
+
239
+ def flush() -> None:
240
+ """Flush any pending telemetry events."""
241
+ if not is_telemetry_enabled():
242
+ return
243
+
244
+ try:
245
+ posthog.flush()
246
+ except Exception:
247
+ # Ignore flush errors
248
+ pass
249
+
250
+
251
+ def shutdown() -> None:
252
+ """Shutdown telemetry and flush pending events."""
253
+ if not is_telemetry_enabled():
254
+ return
255
+
256
+ try:
257
+ posthog.shutdown()
258
+ except Exception:
259
+ # Ignore shutdown errors
260
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -27,6 +27,7 @@ Requires-Dist: python-dotenv>=1.1.0
27
27
  Requires-Dist: black>=24.10.0
28
28
  Requires-Dist: pyjwt>=2.0.0
29
29
  Requires-Dist: httpx>=0.28.1
30
+ Requires-Dist: posthog>=4.1.0
30
31
  Provides-Extra: telemetry
31
32
  Requires-Dist: opentelemetry-api>=1.33.1; extra == "telemetry"
32
33
  Requires-Dist: opentelemetry-sdk>=1.33.1; extra == "telemetry"
@@ -38,9 +39,19 @@ Dynamic: license-file
38
39
  <div align="center">
39
40
  <img src="./golf-banner.png" alt="Golf Banner">
40
41
 
41
- <h1>Golf</h1>
42
+ <br>
42
43
 
43
- <p><strong>Easiest framework for building MCP servers.</strong></p>
44
+ <h1 align="center">
45
+ <br>
46
+ <span style="font-size: 80px;">⛳ Golf</span>
47
+ <br>
48
+ </h1>
49
+
50
+ <h3 align="center">
51
+ Easiest framework for building MCP servers
52
+ </h3>
53
+
54
+ <br>
44
55
 
45
56
  <p>
46
57
  <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
@@ -48,7 +59,9 @@ Dynamic: license-file
48
59
  <a href="https://github.com/golf-mcp/golf/issues"><img src="https://img.shields.io/badge/support-contact%20author-purple.svg" alt="Support"></a>
49
60
  </p>
50
61
 
51
- <p><a href="https://docs.golf.dev">Docs</a></p>
62
+ <p>
63
+ <a href="https://docs.golf.dev"><strong>📚 Documentation</strong></a>
64
+ </p>
52
65
  </div>
53
66
 
54
67
  ## Overview
@@ -87,7 +100,7 @@ cd your-project-name
87
100
  golf build dev
88
101
  golf run
89
102
  ```
90
- This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`). The `dev` command includes hot reloading, so changes to your component files will automatically restart the server.
103
+ This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`).
91
104
 
92
105
  That's it! Your Golf server is running and ready for integration.
93
106
 
@@ -151,29 +164,76 @@ Golf will automatically discover this file. The module docstring `"""Hello World
151
164
 
152
165
  ## Configuration (`golf.json`)
153
166
 
154
- Key aspects of your Golf server are configured in `golf.json`. The boilerplate provides a starting point like this:
167
+ The `golf.json` file is the heart of your Golf project configuration. Here's what each field controls:
155
168
 
156
169
  ```jsonc
157
170
  {
158
- "name": "{{project_name}}", // Will be replaced with your project name
159
- "description": "A Golf project", // A default description
160
- "host": "127.0.0.1", // Server host address
161
- "port": 3000, // Server port
162
- "transport": "sse", // 'sse', 'streamable-http', or 'stdio'
163
- "opentelemetry_enabled": false, // OpenTelemetry disabled by default - we're working on this as a feature
164
- "opentelemetry_default_exporter": "console"
171
+ "name": "{{project_name}}", // Your MCP server name (required)
172
+ "description": "A Golf project", // Brief description of your server's purpose
173
+ "host": "127.0.0.1", // Server host - use "0.0.0.0" to listen on all interfaces
174
+ "port": 3000, // Server port - any available port number
175
+ "transport": "sse", // Communication protocol:
176
+ // - "sse": Server-Sent Events (recommended for web clients)
177
+ // - "streamable-http": HTTP with streaming support
178
+ // - "stdio": Standard I/O (for CLI integration)
165
179
  }
166
180
  ```
167
- ## Roadmap
168
181
 
169
- We are actively developing Golf. Here's what's on our current roadmap:
182
+ ### Key Configuration Options:
183
+
184
+ - **`name`**: The identifier for your MCP server. This will be shown to clients connecting to your server.
185
+ - **`transport`**: Choose based on your client needs:
186
+ - `"sse"` is ideal for web-based clients and real-time communication
187
+ - `"streamable-http"` provides HTTP streaming for traditional API clients
188
+ - `"stdio"` enables integration with command-line tools and scripts
189
+ - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
190
+
191
+ ## Privacy & Telemetry
192
+
193
+ Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
194
+
195
+ - Commands run (init, build, run)
196
+ - Success/failure status (no error details)
197
+ - Golf version, Python version (major.minor only), and OS type
198
+ - Template name (for init command only)
199
+ - Build environment (dev/prod for build commands only)
170
200
 
201
+ **No personal information, project names, code content, or error messages are ever collected.**
202
+
203
+ ### Opting Out
204
+
205
+ You can disable telemetry in several ways:
206
+
207
+ 1. **Using the telemetry command** (recommended):
208
+ ```bash
209
+ golf telemetry disable
210
+ ```
211
+ This saves your preference permanently. To re-enable:
212
+ ```bash
213
+ golf telemetry enable
214
+ ```
215
+
216
+ 2. **During any command**: Add `--no-telemetry` to save your preference:
217
+ ```bash
218
+ golf init my-project --no-telemetry
219
+ ```
220
+
221
+ 3. **Environment variable** (temporary override):
222
+ ```bash
223
+ export GOLF_TELEMETRY=0
224
+ golf init my-project
225
+ ```
226
+
227
+ Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
228
+
229
+ ## Roadmap
171
230
 
172
- ## Documentation
231
+ Here are the things we are working hard on:
173
232
 
174
- For more information, please visit our official documentation:
233
+ * **Native OpenTelemetry implementation for tracing**
234
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
235
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
175
236
 
176
- [https://docs.golf.dev](https://docs.golf.dev)
177
237
 
178
238
  <div align="center">
179
239
  Made with ❤️ in Warsaw, Poland and SF
@@ -1,20 +1,21 @@
1
- golf/__init__.py,sha256=K5SiDdEGYMpdqXThrqwTqECJJBOQNTQDrnpc2K5mzKs,21
1
+ golf/__init__.py,sha256=R5TtpJu7Qu6sOarfDpp-5Oyy8Pi2Ir3VewCvsCQiAgo,21
2
2
  golf/auth/__init__.py,sha256=0cfMJsLsOAsA4gHynyPGDkrjB90s3xsGSFoCEWg-y6o,3999
3
3
  golf/auth/helpers.py,sha256=TqMlOzwtcQcrZ6en2WVkzH0TSQ0RsoDRObpi_rR0Ie4,1681
4
4
  golf/auth/oauth.py,sha256=MGtuKMSt77yihuZiuiEm33xAlXJynIMcFmMLZRlppdg,31525
5
5
  golf/auth/provider.py,sha256=-iIjw1XruxgpWTrwYBX9dbf_sLPlx5vZxZsM0E4Dp64,3931
6
6
  golf/cli/__init__.py,sha256=MG53_GB5QcXHJAwSUKhy3xWLWwzc3FrnFbcCg4z6M0w,45
7
- golf/cli/main.py,sha256=L4EmCu_CJsB42zdh1y3cnzxMZ2Bge5yb7v15durLZ4E,7299
7
+ golf/cli/main.py,sha256=TcFTqFAcFOl2SxPxHFc6jg8OLVtr92KhVZzabpJISNI,9836
8
8
  golf/commands/__init__.py,sha256=FRehcdCr0TrH_399w-PO9OzLhE7z2FVoCFAmfqHsYWo,83
9
9
  golf/commands/build.py,sha256=yhcUrK9gHnXNuLPZ2brcmtMgR8wXMBlEBcaBgTKkanI,2295
10
- golf/commands/init.py,sha256=eVhHW6GIscZrGa8-EgJvKbjc47RkFq6bBc2y95VyK30,6629
11
- golf/commands/run.py,sha256=s0O15_wxxraDFTdjDWAXdnQzWzc2tTh76iS60wQ55Ec,1861
10
+ golf/commands/init.py,sha256=6LMsjYUGOLrp6AVFx6c1WBFe_qE4W-26q4eMDF8nQAM,7105
11
+ golf/commands/run.py,sha256=dLrjmpypfvInDkA-KgEUyKrTvkvp59yea5tUyxk_Ej4,1989
12
12
  golf/core/__init__.py,sha256=RDSMAkB5NDaV7W5OFJ--miqv6JmvM3rI9ashndqM3X4,52
13
13
  golf/core/builder.py,sha256=fUHLAjCxroSN_XJUYT07oC4VGvYMKyrzulDyR_VrwhY,52403
14
14
  golf/core/builder_auth.py,sha256=epc_pYcKcSaphcbJD-DHnFWFjjvQR6EhPETRfVzN4K4,9174
15
15
  golf/core/builder_telemetry.py,sha256=6ip4peRcb-RVZ6djorKW_VW6IiTCbnzOJYQm_YOMvpc,10189
16
- golf/core/config.py,sha256=rzUWmc9lbcEgCZR73k3PucxUb6v4X8psPD2SPXw539M,6886
17
- golf/core/parser.py,sha256=ZXQaNIIwQQvirTHudRPY42M9cmZUCVu5kUPrHxSpgKg,20996
16
+ golf/core/config.py,sha256=gZ3eE8VIvDgIrLTizNq9XqkgfMkRg8Pm06gSVV51arA,6806
17
+ golf/core/parser.py,sha256=sCVMWO7sGkSaKkU9LiHC3O9CTcrZxUbxnjdtwP7ne_o,19251
18
+ golf/core/telemetry.py,sha256=mMqmBZm35BI8IAlRRj0jCAtzcGOprhaZzRslRhLb7dA,7355
18
19
  golf/core/transformer.py,sha256=3mr4_K4l1HZl1WQWSt9NKEhB5oi3b7kJnniseYEfrcI,5573
19
20
  golf/examples/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
20
21
  golf/examples/basic/.env,sha256=CRh4u1yXPCbjjgUKUMUgw51aOBdVFEgY748z7WgfkVc,156
@@ -33,9 +34,9 @@ golf/examples/basic/tools/hello.py,sha256=hJUMraxgi5g0gzhPbUZGOeAbiKZX4BTr1L64SE
33
34
  golf/examples/basic/tools/payments/charge.py,sha256=jLOyQmbpRm8cOIK2JXSirPmQGXDM-yHvg7ruv72j6qU,1287
34
35
  golf/examples/basic/tools/payments/common.py,sha256=z5shSOZizUEnxBmGUSYghxmNwIvuhg-yfXKIfPWgV04,1292
35
36
  golf/examples/basic/tools/payments/refund.py,sha256=_HG4dDeki8FyKCtWs5fXYeLpUsrc_SAx3vxAIS7Icf0,1302
36
- golf_mcp-0.1.2.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
37
- golf_mcp-0.1.2.dist-info/METADATA,sha256=pgiA91H0Gk_AwkI8Y-GDHVpN0TtB4DAnwUIWVjwk3XM,7365
38
- golf_mcp-0.1.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
39
- golf_mcp-0.1.2.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
40
- golf_mcp-0.1.2.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
41
- golf_mcp-0.1.2.dist-info/RECORD,,
37
+ golf_mcp-0.1.3.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
38
+ golf_mcp-0.1.3.dist-info/METADATA,sha256=oG_C5jVawJ_ApzghOZYdbhEJKm5p4bH10b6kOZLm_kU,9390
39
+ golf_mcp-0.1.3.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
40
+ golf_mcp-0.1.3.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
41
+ golf_mcp-0.1.3.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
42
+ golf_mcp-0.1.3.dist-info/RECORD,,