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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.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
- 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,117 +1,61 @@
1
- # {{project_name}}
1
+ # GolfMCP Project Boilerplate
2
2
 
3
- A GolfMCP project that provides MCP-compatible tools, resources, and prompts.
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
- ## Getting Started
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
- This project is built with [GolfMCP](https://github.com/yourusername/golfmcp), a Python framework for building MCP servers with zero boilerplate.
8
+ ## Getting Started (After `golf init`)
8
9
 
9
- To start the development server with hot reload:
10
+ Once you've initialized your new project from this boilerplate:
10
11
 
11
- ```bash
12
- golf dev
13
- ```
12
+ 1. **Navigate to your project directory:**
13
+ ```bash
14
+ cd your-project-name
15
+ ```
14
16
 
15
- This will watch for file changes and automatically reload the server.
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
- - `tools/` - Tool implementations (functions an LLM can call)
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
- ## Adding New Components
25
-
26
- ### Tools
27
-
28
- To add a new tool, create a Python file in the `tools/` directory:
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
- ```python
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
- async def run(input):
86
- result = await payment_client.create_charge(...)
87
- # Rest of implementation...
88
- ```
39
+ To add new functionalities:
89
40
 
90
- This pattern helps organize shared code and makes it easier to build and maintain your project.
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
- ## Telemetry
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
- This project includes OpenTelemetry integration for tracing server requests:
47
+ For shared functionality within a component subdirectory (e.g., `tools/payments/common.py`), you can use a `common.py` file.
95
48
 
96
- ```json
97
- // golf.json
98
- {
99
- "telemetry": true,
100
- "telemetry_exporter": "console" // or "otlp_http"
101
- }
102
- ```
49
+ ## Documentation
103
50
 
104
- You can configure it with environment variables:
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
- ## Deployment
53
+ [https://docs.golf.dev](https://docs.golf.dev)
110
54
 
111
- To build the project for deployment:
55
+ ---
112
56
 
113
- ```bash
114
- golf build
115
- ```
57
+ Happy Building!
116
58
 
117
- This creates a standalone FastMCP application in the `dist/` directory.
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=8oAxKUG747GUokmxjkrWejyJa5yPNEsoJDlXxoedxTw,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
21
22
  golf/examples/basic/.env.example,sha256=gCTFcabTvr_-Rg3kTGmi63JQtCEOwMu4U5jTa1GM66U,123
22
- golf/examples/basic/README.md,sha256=Hp6mQlC2pj_JKd55fO1O50zTxmqA55JldwoVOXUSCPA,2882
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.1.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
37
- golf_mcp-0.1.1.dist-info/METADATA,sha256=mXLS9WPAwviBjcyjW9BRHVOlro9Uc0M90l5YVSLZtm4,2421
38
- golf_mcp-0.1.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
39
- golf_mcp-0.1.1.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
40
- golf_mcp-0.1.1.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
41
- golf_mcp-0.1.1.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,,
@@ -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