golf-mcp 0.1.10__py3-none-any.whl → 0.1.11__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.10"
1
+ __version__ = "0.1.11"
golf/cli/main.py CHANGED
@@ -106,7 +106,7 @@ def build_dev(
106
106
  if not project_root:
107
107
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
108
108
  console.print("Run 'golf init <project_name>' to create a new project.")
109
- track_event("cli_build_failed", {"success": False, "environment": "dev"})
109
+ track_event("cli_build_failed", {"success": False, "environment": "dev", "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
110
110
  raise typer.Exit(code=1)
111
111
 
112
112
  # Load settings from the found project
@@ -124,8 +124,10 @@ def build_dev(
124
124
  build_project(project_root, settings, output_dir, build_env="dev", copy_env=True)
125
125
  # Track successful build with environment
126
126
  track_event("cli_build_success", {"success": True, "environment": "dev"})
127
- except Exception:
128
- track_event("cli_build_failed", {"success": False, "environment": "dev"})
127
+ except Exception as e:
128
+ error_type = type(e).__name__
129
+ error_message = str(e)
130
+ track_event("cli_build_failed", {"success": False, "environment": "dev", "error_type": error_type, "error_message": error_message})
129
131
  raise
130
132
 
131
133
 
@@ -142,7 +144,7 @@ def build_prod(
142
144
  if not project_root:
143
145
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
144
146
  console.print("Run 'golf init <project_name>' to create a new project.")
145
- track_event("cli_build_failed", {"success": False, "environment": "prod"})
147
+ track_event("cli_build_failed", {"success": False, "environment": "prod", "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
146
148
  raise typer.Exit(code=1)
147
149
 
148
150
  # Load settings from the found project
@@ -160,8 +162,10 @@ def build_prod(
160
162
  build_project(project_root, settings, output_dir, build_env="prod", copy_env=False)
161
163
  # Track successful build with environment
162
164
  track_event("cli_build_success", {"success": True, "environment": "prod"})
163
- except Exception:
164
- track_event("cli_build_failed", {"success": False, "environment": "prod"})
165
+ except Exception as e:
166
+ error_type = type(e).__name__
167
+ error_message = str(e)
168
+ track_event("cli_build_failed", {"success": False, "environment": "prod", "error_type": error_type, "error_message": error_message})
165
169
  raise
166
170
 
167
171
 
@@ -191,7 +195,7 @@ def run(
191
195
  if not project_root:
192
196
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
193
197
  console.print("Run 'golf init <project_name>' to create a new project.")
194
- track_event("cli_run_failed", {"success": False})
198
+ track_event("cli_run_failed", {"success": False, "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
195
199
  raise typer.Exit(code=1)
196
200
 
197
201
  # Load settings from the found project
@@ -207,13 +211,20 @@ def run(
207
211
  if not dist_dir.exists():
208
212
  if build_first:
209
213
  console.print(f"[yellow]Dist directory {dist_dir} not found. Building first...[/yellow]")
210
- # Build the project
211
- from golf.commands.build import build_project
212
- build_project(project_root, settings, dist_dir)
214
+ try:
215
+ # Build the project
216
+ from golf.commands.build import build_project
217
+ build_project(project_root, settings, dist_dir)
218
+ except Exception as e:
219
+ error_type = type(e).__name__
220
+ error_message = str(e)
221
+ console.print(f"[bold red]Error building project:[/bold red] {error_message}")
222
+ track_event("cli_run_failed", {"success": False, "error_type": f"BuildError.{error_type}", "error_message": error_message})
223
+ raise
213
224
  else:
214
225
  console.print(f"[bold red]Error: Dist directory {dist_dir} not found.[/bold red]")
215
226
  console.print("Run 'golf build' first or use --build to build automatically.")
216
- track_event("cli_run_failed", {"success": False})
227
+ track_event("cli_run_failed", {"success": False, "error_type": "DistNotFound", "error_message": "Dist directory not found"})
217
228
  raise typer.Exit(code=1)
218
229
 
219
230
  try:
@@ -231,13 +242,15 @@ def run(
231
242
  if return_code == 0:
232
243
  track_event("cli_run_success", {"success": True})
233
244
  else:
234
- track_event("cli_run_failed", {"success": False})
245
+ track_event("cli_run_failed", {"success": False, "error_type": "NonZeroExit", "error_message": f"Server exited with code {return_code}"})
235
246
 
236
247
  # Exit with the same code as the server
237
248
  if return_code != 0:
238
249
  raise typer.Exit(code=return_code)
239
- except Exception:
240
- track_event("cli_run_failed", {"success": False})
250
+ except Exception as e:
251
+ error_type = type(e).__name__
252
+ error_message = str(e)
253
+ track_event("cli_run_failed", {"success": False, "error_type": error_type, "error_message": error_message})
241
254
  raise
242
255
 
243
256
 
golf/commands/init.py CHANGED
@@ -9,7 +9,7 @@ from rich.console import Console
9
9
  from rich.progress import Progress, SpinnerColumn, TextColumn
10
10
  from rich.prompt import Confirm
11
11
 
12
- from golf.core.telemetry import track_event
12
+ from golf.core.telemetry import track_event, track_command
13
13
 
14
14
  console = Console()
15
15
 
@@ -26,72 +26,83 @@ def initialize_project(
26
26
  output_dir: Directory where the project will be created
27
27
  template: Template to use (basic or api_key)
28
28
  """
29
- # Validate template
30
- valid_templates = ("basic", "api_key")
31
- if template not in valid_templates:
32
- console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
33
- console.print(f"Available templates: {', '.join(valid_templates)}")
34
- track_event("cli_init_failed", {"success": False})
35
- return
36
-
37
- # Check if directory exists
38
- if output_dir.exists():
39
- if not output_dir.is_dir():
29
+ try:
30
+ # Validate template
31
+ valid_templates = ("basic", "api_key")
32
+ if template not in valid_templates:
33
+ console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
34
+ console.print(f"Available templates: {', '.join(valid_templates)}")
35
+ track_command("init", success=False, error_type="InvalidTemplate", error_message=f"Unknown template: {template}")
36
+ return
37
+
38
+ # Check if directory exists
39
+ if output_dir.exists():
40
+ if not output_dir.is_dir():
41
+ console.print(
42
+ f"[bold red]Error:[/bold red] '{output_dir}' exists but is not a directory."
43
+ )
44
+ track_command("init", success=False, error_type="NotADirectory", error_message="Target exists but is not a directory")
45
+ return
46
+
47
+ # Check if directory is empty
48
+ if any(output_dir.iterdir()):
49
+ if not Confirm.ask(
50
+ f"Directory '{output_dir}' is not empty. Continue anyway?",
51
+ default=False,
52
+ ):
53
+ console.print("Initialization cancelled.")
54
+ track_event("cli_init_cancelled", {"success": False})
55
+ return
56
+ else:
57
+ # Create the directory
58
+ output_dir.mkdir(parents=True)
59
+
60
+ # Find template directory within the installed package
61
+ import golf
62
+ package_init_file = Path(golf.__file__)
63
+ # The 'examples' directory is now inside the 'golf' package directory
64
+ # e.g. golf/examples/basic, so go up one from __init__.py to get to 'golf'
65
+ template_dir = package_init_file.parent / "examples" / template
66
+
67
+ if not template_dir.exists():
40
68
  console.print(
41
- f"[bold red]Error:[/bold red] '{output_dir}' exists but is not a directory."
69
+ f"[bold red]Error:[/bold red] Could not find template '{template}'"
42
70
  )
43
- track_event("cli_init_failed", {"success": False})
71
+ track_command("init", success=False, error_type="TemplateNotFound", error_message=f"Template directory not found: {template}")
44
72
  return
45
73
 
46
- # Check if directory is empty
47
- if any(output_dir.iterdir()):
48
- if not Confirm.ask(
49
- f"Directory '{output_dir}' is not empty. Continue anyway?",
50
- default=False,
51
- ):
52
- console.print("Initialization cancelled.")
53
- track_event("cli_init_cancelled", {"success": False})
54
- return
55
- else:
56
- # Create the directory
57
- output_dir.mkdir(parents=True)
58
-
59
- # Find template directory within the installed package
60
- import golf
61
- package_init_file = Path(golf.__file__)
62
- # The 'examples' directory is now inside the 'golf' package directory
63
- # e.g. golf/examples/basic, so go up one from __init__.py to get to 'golf'
64
- template_dir = package_init_file.parent / "examples" / template
65
-
66
- if not template_dir.exists():
67
- console.print(
68
- f"[bold red]Error:[/bold red] Could not find template '{template}'"
69
- )
70
- track_event("cli_init_failed", {"success": False})
71
- return
72
-
73
- # Copy template files
74
- with Progress(
75
- SpinnerColumn(),
76
- TextColumn("[bold green]Creating project structure...[/bold green]"),
77
- transient=True,
78
- ) as progress:
79
- progress.add_task("copying", total=None)
74
+ # Copy template files
75
+ with Progress(
76
+ SpinnerColumn(),
77
+ TextColumn("[bold green]Creating project structure...[/bold green]"),
78
+ transient=True,
79
+ ) as progress:
80
+ progress.add_task("copying", total=None)
81
+
82
+ # Copy directory structure
83
+ _copy_template(template_dir, output_dir, project_name)
80
84
 
81
- # Copy directory structure
82
- _copy_template(template_dir, output_dir, project_name)
83
-
84
- # Create virtual environment
85
- console.print("[bold green]Project initialized successfully![/bold green]")
86
- console.print(f"\nTo get started, run:")
87
- console.print(f" cd {output_dir.name}")
88
- console.print(f" golf build dev")
89
-
90
- # Track successful initialization
91
- track_event("cli_init_success", {
92
- "success": True,
93
- "template": template
94
- })
85
+ # Create virtual environment
86
+ console.print("[bold green]Project initialized successfully![/bold green]")
87
+ console.print(f"\nTo get started, run:")
88
+ console.print(f" cd {output_dir.name}")
89
+ console.print(f" golf build dev")
90
+
91
+ # Track successful initialization
92
+ track_event("cli_init_success", {
93
+ "success": True,
94
+ "template": template
95
+ })
96
+ except Exception as e:
97
+ # Capture error details for telemetry
98
+ error_type = type(e).__name__
99
+ error_message = str(e)
100
+
101
+ console.print(f"[bold red]Error during initialization:[/bold red] {error_message}")
102
+ track_command("init", success=False, error_type=error_type, error_message=error_message)
103
+
104
+ # Re-raise to maintain existing behavior
105
+ raise
95
106
 
96
107
 
97
108
  def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> None:
golf/core/builder.py CHANGED
@@ -765,14 +765,25 @@ class CodeGenerator:
765
765
  " # For SSE with API key auth, we need to get the app and add middleware",
766
766
  " app = mcp.http_app(transport=\"sse\")",
767
767
  " app.add_middleware(ApiKeyMiddleware)",
768
- " # Run with the configured app",
769
- " uvicorn.run(app, host=host, port=port, log_level=\"info\")"
770
768
  ])
771
769
  else:
772
770
  main_code.extend([
773
- " # For SSE, FastMCP's run method handles auth integration better",
774
- " mcp.run(transport=\"sse\", host=host, port=port, log_level=\"info\")"
771
+ " # For SSE, get the app to add middleware",
772
+ " app = mcp.http_app(transport=\"sse\")",
775
773
  ])
774
+
775
+ # Add OpenTelemetry middleware to the SSE app if enabled
776
+ if self.settings.opentelemetry_enabled:
777
+ main_code.extend([
778
+ " # Apply OpenTelemetry middleware to the SSE app",
779
+ " from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware",
780
+ " app = OpenTelemetryMiddleware(app)",
781
+ ])
782
+
783
+ main_code.extend([
784
+ " # Run with the configured app",
785
+ " uvicorn.run(app, host=host, port=port, log_level=\"info\")"
786
+ ])
776
787
  elif self.settings.transport in ["streamable-http", "http"]:
777
788
  main_code.extend([
778
789
  " # Create HTTP app and run with uvicorn",
golf/core/telemetry.py CHANGED
@@ -243,7 +243,7 @@ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) ->
243
243
  # Filter properties to only include safe ones
244
244
  if properties:
245
245
  # Only include specific safe properties
246
- safe_keys = {"success", "environment", "template", "command_type"}
246
+ safe_keys = {"success", "environment", "template", "command_type", "error_type", "error_message"}
247
247
  for key in safe_keys:
248
248
  if key in properties:
249
249
  safe_properties[key] = properties[key]
@@ -260,15 +260,68 @@ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) ->
260
260
  pass
261
261
 
262
262
 
263
- def track_command(command: str, success: bool = True) -> None:
263
+ def track_command(command: str, success: bool = True, error_type: Optional[str] = None, error_message: Optional[str] = None) -> None:
264
264
  """Track a CLI command execution with minimal info.
265
265
 
266
266
  Args:
267
267
  command: The command being executed (e.g., "init", "build", "run")
268
268
  success: Whether the command was successful
269
+ error_type: Type of error if command failed (e.g., "ValueError", "FileNotFoundError")
270
+ error_message: Sanitized error message (no sensitive data)
269
271
  """
270
- # Simplify the event to just command and success
271
- track_event(f"cli_{command}", {"success": success})
272
+ properties = {"success": success}
273
+
274
+ # Add error details if command failed
275
+ if not success and (error_type or error_message):
276
+ if error_type:
277
+ properties["error_type"] = error_type
278
+ if error_message:
279
+ # Sanitize error message - remove file paths and sensitive info
280
+ sanitized_message = _sanitize_error_message(error_message)
281
+ properties["error_message"] = sanitized_message
282
+
283
+ track_event(f"cli_{command}", properties)
284
+
285
+
286
+ def _sanitize_error_message(message: str) -> str:
287
+ """Sanitize error message to remove sensitive information.
288
+
289
+ Args:
290
+ message: Raw error message
291
+
292
+ Returns:
293
+ Sanitized error message
294
+ """
295
+ import re
296
+
297
+ # Remove absolute file paths but keep the filename
298
+ # Unix-style paths
299
+ message = re.sub(r'/(?:Users|home|var|tmp|opt|usr|etc)/[^\s"\']+/([^/\s"\']+)', r'\1', message)
300
+ # Windows-style paths
301
+ message = re.sub(r'[A-Za-z]:\\[^\s"\']+\\([^\\s"\']+)', r'\1', message)
302
+ # Generic path pattern (catches remaining paths)
303
+ message = re.sub(r'(?:^|[\s"])(/[^\s"\']+/)+([^/\s"\']+)', r'\2', message)
304
+
305
+ # Remove potential API keys or tokens (common patterns)
306
+ # Generic API keys (20+ alphanumeric with underscores/hyphens)
307
+ message = re.sub(r'\b[a-zA-Z0-9_-]{32,}\b', '[REDACTED]', message)
308
+ # Bearer tokens
309
+ message = re.sub(r'Bearer\s+[a-zA-Z0-9_.-]+', 'Bearer [REDACTED]', message)
310
+
311
+ # Remove email addresses
312
+ message = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '[EMAIL]', message)
313
+
314
+ # Remove IP addresses
315
+ message = re.sub(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b', '[IP]', message)
316
+
317
+ # Remove port numbers in URLs
318
+ message = re.sub(r':[0-9]{2,5}(?=/|$|\s)', ':[PORT]', message)
319
+
320
+ # Truncate to reasonable length
321
+ if len(message) > 200:
322
+ message = message[:197] + "..."
323
+
324
+ return message
272
325
 
273
326
 
274
327
  def flush() -> None: