golf-mcp 0.1.1__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/examples/basic/README.md +40 -96
- golf_mcp-0.1.3.dist-info/METADATA +240 -0
- {golf_mcp-0.1.1.dist-info → golf_mcp-0.1.3.dist-info}/RECORD +14 -13
- golf_mcp-0.1.1.dist-info/METADATA +0 -78
- {golf_mcp-0.1.1.dist-info → golf_mcp-0.1.3.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.1.dist-info → golf_mcp-0.1.3.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.1.dist-info → golf_mcp-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.1.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
|
golf/examples/basic/README.md
CHANGED
|
@@ -1,117 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# GolfMCP Project Boilerplate
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This directory serves as a starting template for new GolfMCP projects. Initialize a project using the `golf init <your-project-name>` command.
|
|
4
|
+
## About GolfMCP
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
GolfMCP is a Python framework designed to build MCP servers with minimal boilerplate. It allows you to define tools, resources, and prompts as simple Python files. These components are then automatically discovered and compiled into a runnable [FastMCP](https://github.com/fastmcp/fastmcp) server.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Getting Started (After `golf init`)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Once you've initialized your new project from this boilerplate:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
1. **Navigate to your project directory:**
|
|
13
|
+
```bash
|
|
14
|
+
cd your-project-name
|
|
15
|
+
```
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
2. **Start the development server:**
|
|
18
|
+
```bash
|
|
19
|
+
golf build dev # For a development build
|
|
20
|
+
# or
|
|
21
|
+
golf build prod # For a production build
|
|
22
|
+
|
|
23
|
+
golf run
|
|
24
|
+
```
|
|
16
25
|
|
|
17
26
|
## Project Structure
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
- `resources/` - Resource implementations (data an LLM can read)
|
|
21
|
-
- `prompts/` - Prompt templates (conversations an LLM can use)
|
|
22
|
-
- `golf.json` - Configuration file with settings like telemetry and transport
|
|
28
|
+
Your initialized GolfMCP project will typically have the following structure:
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```python
|
|
31
|
-
# tools/my_tool.py
|
|
32
|
-
from pydantic import BaseModel
|
|
33
|
-
from fastmcp import Context
|
|
34
|
-
|
|
35
|
-
class Input(BaseModel):
|
|
36
|
-
param1: str
|
|
37
|
-
param2: int = 42
|
|
38
|
-
|
|
39
|
-
class Output(BaseModel):
|
|
40
|
-
result: str
|
|
41
|
-
|
|
42
|
-
async def run(input: Input, ctx: Context) -> Output:
|
|
43
|
-
"""Description of what my tool does."""
|
|
44
|
-
await ctx.info(f"Processing {input.param1}...")
|
|
45
|
-
return MyOutput(result=f"Processed {input.param1} with {input.param2}")
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Resources
|
|
49
|
-
|
|
50
|
-
To add a new resource, create a Python file in the `resources/` directory:
|
|
30
|
+
- `tools/`: Directory for your tool implementations (Python files defining functions an LLM can call).
|
|
31
|
+
- `resources/`: Directory for your resource implementations (Python files defining data an LLM can read).
|
|
32
|
+
- `prompts/`: Directory for your prompt templates (Python files defining reusable conversation structures).
|
|
33
|
+
- `golf.json`: The main configuration file for your project, including settings like the server name, port, and transport.
|
|
34
|
+
- `pre_build.py`: (Optional) A Python script that can be used to run custom logic before the build process begins, such as configuring authentication.
|
|
35
|
+
- `.env`: File to store environment-specific variables (e.g., API keys). This is created during `golf init`.
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
# resources/my_data.py
|
|
54
|
-
resource_uri = "data://my-data"
|
|
55
|
-
|
|
56
|
-
async def run() -> dict:
|
|
57
|
-
"""Description of the resource."""
|
|
58
|
-
return {
|
|
59
|
-
"title": "My Data",
|
|
60
|
-
"content": "Some valuable information"
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Sharing Functionality with common.py
|
|
65
|
-
|
|
66
|
-
For directories with multiple related components (like `tools/payments/` or `resources/weather/`),
|
|
67
|
-
use a `common.py` file to share functionality:
|
|
68
|
-
|
|
69
|
-
```python
|
|
70
|
-
# tools/payments/common.py
|
|
71
|
-
class PaymentClient:
|
|
72
|
-
"""Shared payment client implementation."""
|
|
73
|
-
# Implementation details...
|
|
74
|
-
|
|
75
|
-
# Create a shared client instance
|
|
76
|
-
payment_client = PaymentClient()
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Then import and use the shared functionality in your components:
|
|
80
|
-
|
|
81
|
-
```python
|
|
82
|
-
# tools/payments/charge.py
|
|
83
|
-
from .common import payment_client
|
|
37
|
+
## Adding New Components
|
|
84
38
|
|
|
85
|
-
|
|
86
|
-
result = await payment_client.create_charge(...)
|
|
87
|
-
# Rest of implementation...
|
|
88
|
-
```
|
|
39
|
+
To add new functionalities:
|
|
89
40
|
|
|
90
|
-
|
|
41
|
+
- **Tools**: Create a new `.py` file in the `tools/` directory.
|
|
42
|
+
- **Resources**: Create a new `.py` file in the `resources/` directory.
|
|
43
|
+
- **Prompts**: Create a new `.py` file in the `prompts/` directory.
|
|
91
44
|
|
|
92
|
-
|
|
45
|
+
Each Python file should generally define a single component. A module-level docstring in the file will be used as the description for the component. See the example files (e.g., `tools/hello.py`, `resources/info.py`) provided in this boilerplate for reference.
|
|
93
46
|
|
|
94
|
-
|
|
47
|
+
For shared functionality within a component subdirectory (e.g., `tools/payments/common.py`), you can use a `common.py` file.
|
|
95
48
|
|
|
96
|
-
|
|
97
|
-
// golf.json
|
|
98
|
-
{
|
|
99
|
-
"telemetry": true,
|
|
100
|
-
"telemetry_exporter": "console" // or "otlp_http"
|
|
101
|
-
}
|
|
102
|
-
```
|
|
49
|
+
## Documentation
|
|
103
50
|
|
|
104
|
-
|
|
105
|
-
- `OTEL_SERVICE_NAME`: Set service name (defaults to app name)
|
|
106
|
-
- `OTEL_TRACES_EXPORTER`: Exporter type ("console" or "otlp_http")
|
|
107
|
-
- `OTEL_EXPORTER_OTLP_ENDPOINT`: OTLP exporter endpoint URL
|
|
51
|
+
For comprehensive details on the GolfMCP framework, including component specifications, advanced configurations, CLI commands, and more, please refer to the official documentation:
|
|
108
52
|
|
|
109
|
-
|
|
53
|
+
[https://docs.golf.dev](https://docs.golf.dev)
|
|
110
54
|
|
|
111
|
-
|
|
55
|
+
---
|
|
112
56
|
|
|
113
|
-
|
|
114
|
-
golf build
|
|
115
|
-
```
|
|
57
|
+
Happy Building!
|
|
116
58
|
|
|
117
|
-
|
|
59
|
+
<div align="center">
|
|
60
|
+
Made with ❤️ in San Francisco
|
|
61
|
+
</div>
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: golf-mcp
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Framework for building MCP servers
|
|
5
|
+
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://golf.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/golf-mcp/golf
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: typer>=0.15.4
|
|
23
|
+
Requires-Dist: rich>=14.0.0
|
|
24
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
25
|
+
Requires-Dist: pydantic>=2.11.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.1.0
|
|
27
|
+
Requires-Dist: black>=24.10.0
|
|
28
|
+
Requires-Dist: pyjwt>=2.0.0
|
|
29
|
+
Requires-Dist: httpx>=0.28.1
|
|
30
|
+
Requires-Dist: posthog>=4.1.0
|
|
31
|
+
Provides-Extra: telemetry
|
|
32
|
+
Requires-Dist: opentelemetry-api>=1.33.1; extra == "telemetry"
|
|
33
|
+
Requires-Dist: opentelemetry-sdk>=1.33.1; extra == "telemetry"
|
|
34
|
+
Requires-Dist: opentelemetry-instrumentation-asgi>=0.40b0; extra == "telemetry"
|
|
35
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=0.40b0; extra == "telemetry"
|
|
36
|
+
Requires-Dist: wrapt>=1.17.0; extra == "telemetry"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
<div align="center">
|
|
40
|
+
<img src="./golf-banner.png" alt="Golf Banner">
|
|
41
|
+
|
|
42
|
+
<br>
|
|
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>
|
|
55
|
+
|
|
56
|
+
<p>
|
|
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>
|
|
58
|
+
<a href="https://github.com/golf-mcp/golf/pulls"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs"></a>
|
|
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>
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
<p>
|
|
63
|
+
<a href="https://docs.golf.dev"><strong>📚 Documentation</strong></a>
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
## Overview
|
|
68
|
+
|
|
69
|
+
Golf is a **framework** designed to streamline the creation of MCP server applications. It allows developers to define server's capabilities—*tools*, *prompts*, and *resources*—as simple Python files within a conventional directory structure. Golf then automatically discovers, parses, and compiles these components into a runnable FastMCP server, minimizing boilerplate and accelerating development.
|
|
70
|
+
|
|
71
|
+
With Golf, you can focus on implementing your agent's logic rather than wrestling with server setup and integration complexities. It's built for developers who want a quick, organized way to build powerful MCP servers.
|
|
72
|
+
|
|
73
|
+
## Quick Start
|
|
74
|
+
|
|
75
|
+
Get your Golf project up and running in a few simple steps:
|
|
76
|
+
|
|
77
|
+
### 1. Install Golf
|
|
78
|
+
|
|
79
|
+
Ensure you have Python (3.10+ recommended) installed. Then, install Golf using pip:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install golf-mcp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Initialize Your Project
|
|
86
|
+
|
|
87
|
+
Use the Golf CLI to scaffold a new project:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
golf init your-project-name
|
|
91
|
+
```
|
|
92
|
+
This command creates a new directory (`your-project-name`) with a basic project structure, including example tools, resources, and a `golf.json` configuration file.
|
|
93
|
+
|
|
94
|
+
### 3. Run the Development Server
|
|
95
|
+
|
|
96
|
+
Navigate into your new project directory and start the development server:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cd your-project-name
|
|
100
|
+
golf build dev
|
|
101
|
+
golf run
|
|
102
|
+
```
|
|
103
|
+
This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`).
|
|
104
|
+
|
|
105
|
+
That's it! Your Golf server is running and ready for integration.
|
|
106
|
+
|
|
107
|
+
## Basic Project Structure
|
|
108
|
+
|
|
109
|
+
A Golf project initialized with `golf init` will have a structure similar to this:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
<your-project-name>/
|
|
113
|
+
│
|
|
114
|
+
├─ golf.json # Main project configuration
|
|
115
|
+
│
|
|
116
|
+
├─ tools/ # Directory for tool implementations
|
|
117
|
+
│ └─ hello.py # Example tool
|
|
118
|
+
│
|
|
119
|
+
├─ resources/ # Directory for resource implementations
|
|
120
|
+
│ └─ info.py # Example resource
|
|
121
|
+
│
|
|
122
|
+
├─ prompts/ # Directory for prompt templates
|
|
123
|
+
│ └─ welcome.py # Example prompt
|
|
124
|
+
│
|
|
125
|
+
├─ .env # Environment variables (e.g., API keys, server port)
|
|
126
|
+
└─ pre_build.py # (Optional) Script for pre-build hooks (e.g., auth setup)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
|
|
130
|
+
- **`tools/`**, **`resources/`**, **`prompts/`**: Contain your Python files, each defining a single component. These directories can also contain nested subdirectories to further organize your components (e.g., `tools/payments/charge.py`). The module docstring of each file serves as the component's description.
|
|
131
|
+
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit-payments` (filename, followed by reversed parent directories under the main category, joined by hyphens).
|
|
132
|
+
- **`common.py`** (not shown, but can be placed in subdirectories like `tools/payments/common.py`): Used to share code (clients, models, etc.) among components in the same subdirectory.
|
|
133
|
+
|
|
134
|
+
## Example: Defining a Tool
|
|
135
|
+
|
|
136
|
+
Creating a new tool is as simple as adding a Python file to the `tools/` directory. The example `tools/hello.py` in the boilerplate looks like this:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# tools/hello.py
|
|
140
|
+
"""Hello World tool {{project_name}}."""
|
|
141
|
+
|
|
142
|
+
from pydantic import BaseModel
|
|
143
|
+
|
|
144
|
+
class Output(BaseModel):
|
|
145
|
+
"""Response from the hello tool."""
|
|
146
|
+
message: str
|
|
147
|
+
|
|
148
|
+
async def hello(
|
|
149
|
+
name: str = "World",
|
|
150
|
+
greeting: str = "Hello"
|
|
151
|
+
) -> Output:
|
|
152
|
+
"""Say hello to the given name.
|
|
153
|
+
|
|
154
|
+
This is a simple example tool that demonstrates the basic structure
|
|
155
|
+
of a tool implementation in Golf.
|
|
156
|
+
"""
|
|
157
|
+
print(f"{greeting} {name}...")
|
|
158
|
+
return Output(message=f"{greeting}, {name}!")
|
|
159
|
+
|
|
160
|
+
# Designate the entry point function
|
|
161
|
+
export = hello
|
|
162
|
+
```
|
|
163
|
+
Golf will automatically discover this file. The module docstring `"""Hello World tool {{project_name}}."""` is used as the tool's description. It infers parameters from the `hello` function's signature and uses the `Output` Pydantic model for the output schema. The tool will be registered with the ID `hello`.
|
|
164
|
+
|
|
165
|
+
## Configuration (`golf.json`)
|
|
166
|
+
|
|
167
|
+
The `golf.json` file is the heart of your Golf project configuration. Here's what each field controls:
|
|
168
|
+
|
|
169
|
+
```jsonc
|
|
170
|
+
{
|
|
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)
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
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)
|
|
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
|
|
230
|
+
|
|
231
|
+
Here are the things we are working hard on:
|
|
232
|
+
|
|
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**
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
<div align="center">
|
|
239
|
+
Made with ❤️ in Warsaw, Poland and SF
|
|
240
|
+
</div>
|
|
@@ -1,25 +1,26 @@
|
|
|
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
|
|
21
22
|
golf/examples/basic/.env.example,sha256=gCTFcabTvr_-Rg3kTGmi63JQtCEOwMu4U5jTa1GM66U,123
|
|
22
|
-
golf/examples/basic/README.md,sha256
|
|
23
|
+
golf/examples/basic/README.md,sha256=-mY3R6AAnkXT9FPkALDrJtdf9IyKDvuqjsrLAMTLRYI,2663
|
|
23
24
|
golf/examples/basic/golf.json,sha256=JThi6EC93trGqECo3ryD3MhDcyR9Haub8kYvmYo4yjQ,213
|
|
24
25
|
golf/examples/basic/pre_build.py,sha256=VVYtBSCy4Xjg1Ocpl8ZwEr7VCVqPSSkDEzrTfQkAQKY,1037
|
|
25
26
|
golf/examples/basic/prompts/welcome.py,sha256=LgT1eQewe0J08I5WFBv4RrgDr6yn4a4ww6XQ8k3sTVk,822
|
|
@@ -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,,
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: golf-mcp
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: Framework for building MCP servers
|
|
5
|
-
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
|
-
License-Expression: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://golf.dev
|
|
8
|
-
Project-URL: Repository, https://github.com/golf-mcp/golf
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Requires-Python: >=3.8
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENSE
|
|
22
|
-
Requires-Dist: typer>=0.15.4
|
|
23
|
-
Requires-Dist: rich>=14.0.0
|
|
24
|
-
Requires-Dist: fastmcp>=2.0.0
|
|
25
|
-
Requires-Dist: pydantic>=2.11.0
|
|
26
|
-
Requires-Dist: python-dotenv>=1.1.0
|
|
27
|
-
Requires-Dist: black>=24.10.0
|
|
28
|
-
Requires-Dist: pyjwt>=2.0.0
|
|
29
|
-
Requires-Dist: httpx>=0.28.1
|
|
30
|
-
Provides-Extra: telemetry
|
|
31
|
-
Requires-Dist: opentelemetry-api>=1.33.1; extra == "telemetry"
|
|
32
|
-
Requires-Dist: opentelemetry-sdk>=1.33.1; extra == "telemetry"
|
|
33
|
-
Requires-Dist: opentelemetry-instrumentation-asgi>=0.40b0; extra == "telemetry"
|
|
34
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=0.40b0; extra == "telemetry"
|
|
35
|
-
Requires-Dist: wrapt>=1.17.0; extra == "telemetry"
|
|
36
|
-
Dynamic: license-file
|
|
37
|
-
|
|
38
|
-
# GolfMCP
|
|
39
|
-
|
|
40
|
-
A Pythonic framework for building Model Context Protocol (MCP) servers with zero boilerplate.
|
|
41
|
-
|
|
42
|
-
## Features
|
|
43
|
-
|
|
44
|
-
* 🚀 **Zero-to-live in under 90 seconds**: Quick setup and deployment
|
|
45
|
-
* 💡 **Convention over configuration**: Minimal boilerplate
|
|
46
|
-
* 🔄 **Fast feedback**: Hot-reload during development (≤ 500 ms)
|
|
47
|
-
* 📂 **Nested folder support**: Organize your tools, resources, and prompts
|
|
48
|
-
* 🔌 **Pluggable auth & deploy**: Built-in providers for OAuth, Vercel, Fly.io, etc.
|
|
49
|
-
* 🛠️ **Delegates protocol compliance to FastMCP**: Focus on your logic, not the wire format
|
|
50
|
-
|
|
51
|
-
## Installation
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
pip install golfmcp
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Quick Start
|
|
58
|
-
|
|
59
|
-
Initialize a new project:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
golf init my-mcp-project
|
|
63
|
-
cd my-mcp-project
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Start the development server with hot-reload:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
golf dev
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Documentation
|
|
73
|
-
|
|
74
|
-
For detailed documentation, visit [docs](https://golfmcp.docs.example.com/).
|
|
75
|
-
|
|
76
|
-
## License
|
|
77
|
-
|
|
78
|
-
MIT License
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|