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 +1 -1
- golf/cli/main.py +79 -23
- golf/commands/init.py +14 -0
- golf/commands/run.py +5 -1
- golf/core/config.py +0 -1
- golf/core/parser.py +1 -42
- golf/core/telemetry.py +260 -0
- {golf_mcp-0.1.2.dist-info → golf_mcp-0.1.3.dist-info}/METADATA +78 -18
- {golf_mcp-0.1.2.dist-info → golf_mcp-0.1.3.dist-info}/RECORD +13 -12
- {golf_mcp-0.1.2.dist-info → golf_mcp-0.1.3.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.2.dist-info → golf_mcp-0.1.3.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.2.dist-info → golf_mcp-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.2.dist-info → golf_mcp-0.1.3.dist-info}/top_level.txt +0 -0
golf/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
<
|
|
42
|
+
<br>
|
|
42
43
|
|
|
43
|
-
<
|
|
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
|
|
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`).
|
|
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
|
-
|
|
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}}", //
|
|
159
|
-
"description": "A Golf project",
|
|
160
|
-
"host": "127.0.0.1",
|
|
161
|
-
"port": 3000,
|
|
162
|
-
"transport": "sse",
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
+
Here are the things we are working hard on:
|
|
173
232
|
|
|
174
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
11
|
-
golf/commands/run.py,sha256=
|
|
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=
|
|
17
|
-
golf/core/parser.py,sha256=
|
|
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.
|
|
37
|
-
golf_mcp-0.1.
|
|
38
|
-
golf_mcp-0.1.
|
|
39
|
-
golf_mcp-0.1.
|
|
40
|
-
golf_mcp-0.1.
|
|
41
|
-
golf_mcp-0.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|