golf-mcp 0.1.14__py3-none-any.whl → 0.1.17__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.14"
1
+ __version__ = "0.1.17"
golf/cli/main.py CHANGED
@@ -121,7 +121,11 @@ def build_dev(
121
121
  None, "--output-dir", "-o", help="Directory to output the built project"
122
122
  ),
123
123
  ) -> None:
124
- """Build a development version with environment variables copied."""
124
+ """Build a development version with app environment variables copied.
125
+
126
+ Golf credentials (GOLF_*) are always loaded from .env for build operations.
127
+ All environment variables are copied to the built project for development.
128
+ """
125
129
  # Find project root directory
126
130
  project_root, config_path = find_project_root()
127
131
 
@@ -162,7 +166,7 @@ def build_dev(
162
166
  e,
163
167
  context="Development build with environment variables",
164
168
  operation="build_dev",
165
- additional_props={"environment": "dev", "copy_env": True}
169
+ additional_props={"environment": "dev", "copy_env": True},
166
170
  )
167
171
  raise
168
172
 
@@ -173,7 +177,14 @@ def build_prod(
173
177
  None, "--output-dir", "-o", help="Directory to output the built project"
174
178
  ),
175
179
  ) -> None:
176
- """Build a production version without copying environment variables."""
180
+ """Build a production version for deployment.
181
+
182
+ Golf credentials (GOLF_*) are always loaded from .env for build operations
183
+ (platform registration, resource updates). App environment variables are
184
+ NOT copied for security - provide them in your deployment environment.
185
+
186
+ Your production deployment must include GOLF_* vars for runtime telemetry.
187
+ """
177
188
  # Find project root directory
178
189
  project_root, config_path = find_project_root()
179
190
 
@@ -214,7 +225,7 @@ def build_prod(
214
225
  e,
215
226
  context="Production build without environment variables",
216
227
  operation="build_prod",
217
- additional_props={"environment": "prod", "copy_env": False}
228
+ additional_props={"environment": "prod", "copy_env": False},
218
229
  )
219
230
  raise
220
231
 
@@ -275,15 +286,13 @@ def run(
275
286
 
276
287
  build_project(project_root, settings, dist_dir)
277
288
  except Exception as e:
278
- console.print(
279
- f"[bold red]Error building project:[/bold red] {str(e)}"
280
- )
289
+ console.print(f"[bold red]Error building project:[/bold red] {str(e)}")
281
290
  track_detailed_error(
282
291
  "cli_run_failed",
283
292
  e,
284
293
  context="Auto-build before running server",
285
294
  operation="auto_build_before_run",
286
- additional_props={"auto_build": True}
295
+ additional_props={"auto_build": True},
287
296
  )
288
297
  raise
289
298
  else:
@@ -326,11 +335,11 @@ def run(
326
335
  # 2: General interrupt/graceful shutdown
327
336
  shutdown_type = {
328
337
  130: "UserInterrupt",
329
- 143: "GracefulShutdown",
338
+ 143: "GracefulShutdown",
330
339
  137: "ForcedShutdown",
331
- 2: "Interrupt"
340
+ 2: "Interrupt",
332
341
  }.get(return_code, "GracefulShutdown")
333
-
342
+
334
343
  track_event(
335
344
  "cli_run_shutdown",
336
345
  {
@@ -362,7 +371,7 @@ def run(
362
371
  e,
363
372
  context="Server execution or startup failure",
364
373
  operation="run_server_execution",
365
- additional_props={"has_dist_dir": dist_dir.exists() if dist_dir else False}
374
+ additional_props={"has_dist_dir": dist_dir.exists() if dist_dir else False},
366
375
  )
367
376
  raise
368
377
 
golf/commands/init.py CHANGED
@@ -7,7 +7,12 @@ from rich.console import Console
7
7
  from rich.progress import Progress, SpinnerColumn, TextColumn
8
8
  from rich.prompt import Confirm
9
9
 
10
- from golf.core.telemetry import track_command, track_event
10
+ from golf.core.telemetry import (
11
+ track_command,
12
+ track_event,
13
+ set_telemetry_enabled,
14
+ load_telemetry_preference,
15
+ )
11
16
 
12
17
  console = Console()
13
18
 
@@ -95,6 +100,9 @@ def initialize_project(
95
100
  # Copy directory structure
96
101
  _copy_template(template_dir, output_dir, project_name)
97
102
 
103
+ # Ask for telemetry consent
104
+ _prompt_for_telemetry_consent()
105
+
98
106
  # Create virtual environment
99
107
  console.print("[bold green]Project initialized successfully![/bold green]")
100
108
  console.print("\nTo get started, run:")
@@ -207,6 +215,60 @@ def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> Non
207
215
  f.write("dist/\n")
208
216
 
209
217
 
218
+ def _prompt_for_telemetry_consent() -> None:
219
+ """Prompt user for telemetry consent and save their preference."""
220
+ import os
221
+
222
+ # Skip prompt in test mode, when telemetry is explicitly disabled, or if preference already exists
223
+ if os.environ.get("GOLF_TEST_MODE", "").lower() in ("1", "true", "yes", "on"):
224
+ return
225
+
226
+ # Skip if telemetry is explicitly disabled in environment
227
+ if os.environ.get("GOLF_TELEMETRY", "").lower() in ("0", "false", "no", "off"):
228
+ return
229
+
230
+ # Check if user already has a saved preference
231
+ existing_preference = load_telemetry_preference()
232
+ if existing_preference is not None:
233
+ return # User already made a choice
234
+
235
+ console.print()
236
+ console.rule("[bold blue]Anonymous usage analytics[/bold blue]", style="blue")
237
+ console.print()
238
+ console.print(
239
+ "Golf can collect [bold]anonymous usage analytics[/bold] to help improve the tool."
240
+ )
241
+ console.print()
242
+ console.print("[dim]What we collect:[/dim]")
243
+ console.print(" • Command usage (init, build, run)")
244
+ console.print(" • Error types (to fix bugs)")
245
+ console.print(" • Golf version and Python version")
246
+ console.print(" • Operating system type")
247
+ console.print()
248
+ console.print("[dim]What we DON'T collect:[/dim]")
249
+ console.print(" • Your code or project content")
250
+ console.print(" • File paths or project names")
251
+ console.print(" • Personal information")
252
+ console.print(" • IP addresses")
253
+ console.print()
254
+ console.print(
255
+ "You can change this anytime by setting GOLF_TELEMETRY=0 in your environment."
256
+ )
257
+ console.print()
258
+
259
+ enable_telemetry = Confirm.ask(
260
+ "[bold]Enable anonymous usage analytics?[/bold]", default=False
261
+ )
262
+
263
+ set_telemetry_enabled(enable_telemetry, persist=True)
264
+
265
+ if enable_telemetry:
266
+ console.print("[green]✓[/green] Anonymous analytics enabled")
267
+ else:
268
+ console.print("[yellow]○[/yellow] Anonymous analytics disabled")
269
+ console.print()
270
+
271
+
210
272
  def _is_text_file(path: Path) -> bool:
211
273
  """Check if a file is a text file that needs variable substitution.
212
274
 
golf/commands/run.py CHANGED
@@ -70,13 +70,21 @@ def run_server(
70
70
  elif process.returncode == 130:
71
71
  console.print("[yellow]Server stopped by user interrupt (Ctrl+C)[/yellow]")
72
72
  elif process.returncode == 143:
73
- console.print("[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]")
73
+ console.print(
74
+ "[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]"
75
+ )
74
76
  elif process.returncode == 137:
75
- console.print("[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]")
77
+ console.print(
78
+ "[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]"
79
+ )
76
80
  elif process.returncode in [1, 2]:
77
- console.print(f"[red]Server exited with error code {process.returncode}[/red]")
81
+ console.print(
82
+ f"[red]Server exited with error code {process.returncode}[/red]"
83
+ )
78
84
  else:
79
- console.print(f"[orange]Server exited with code {process.returncode}[/orange]")
85
+ console.print(
86
+ f"[orange]Server exited with code {process.returncode}[/orange]"
87
+ )
80
88
 
81
89
  return process.returncode
82
90
  except KeyboardInterrupt:
golf/core/builder.py CHANGED
@@ -97,6 +97,11 @@ class ManifestBuilder:
97
97
  if required_fields:
98
98
  tool_schema["inputSchema"]["required"] = required_fields
99
99
 
100
+ # Add tool annotations if present
101
+ if component.annotations:
102
+ # Merge with existing annotations (keeping title)
103
+ tool_schema["annotations"].update(component.annotations)
104
+
100
105
  # Add the tool to the manifest
101
106
  self.manifest["tools"].append(tool_schema)
102
107
 
@@ -556,14 +561,15 @@ class CodeGenerator:
556
561
  # Add OpenTelemetry imports if enabled
557
562
  if self.settings.opentelemetry_enabled:
558
563
  imports.extend(generate_telemetry_imports())
559
- imports.append("")
560
564
 
561
- # Add imports section for different transport methods
562
- if self.settings.transport == "sse" or self.settings.transport in [
563
- "streamable-http",
564
- "http",
565
- ]:
566
- imports.append("import uvicorn")
565
+ # Add health check imports if enabled
566
+ if self.settings.health_check_enabled:
567
+ imports.extend(
568
+ [
569
+ "from starlette.requests import Request",
570
+ "from starlette.responses import PlainTextResponse",
571
+ ]
572
+ )
567
573
 
568
574
  # Get transport-specific configuration
569
575
  transport_config = self._get_transport_config(self.settings.transport)
@@ -650,7 +656,10 @@ class CodeGenerator:
650
656
  # Add code to register this component
651
657
  if self.settings.opentelemetry_enabled:
652
658
  # Use telemetry instrumentation
653
- registration = f"# Register the {component_type.value} '{component.name}' with telemetry"
659
+ registration = (
660
+ f"# Register the {component_type.value} "
661
+ f"'{component.name}' with telemetry"
662
+ )
654
663
  entry_func = (
655
664
  component.entry_function
656
665
  if hasattr(component, "entry_function")
@@ -658,15 +667,31 @@ class CodeGenerator:
658
667
  else "export"
659
668
  )
660
669
 
661
- # Debug: Add logging to verify wrapping
662
- registration += f"\n_wrapped_func = instrument_{component_type.value}({full_module_path}.{entry_func}, '{component.name}')"
670
+ registration += (
671
+ f"\n_wrapped_func = instrument_{component_type.value}("
672
+ f"{full_module_path}.{entry_func}, '{component.name}')"
673
+ )
663
674
 
664
675
  if component_type == ComponentType.TOOL:
665
- registration += f'\nmcp.add_tool(_wrapped_func, name="{component.name}", description="{component.docstring or ""}")'
676
+ registration += (
677
+ f'\nmcp.add_tool(_wrapped_func, name="{component.name}", '
678
+ f'description="{component.docstring or ""}"'
679
+ )
680
+ # Add annotations if present
681
+ if hasattr(component, "annotations") and component.annotations:
682
+ registration += f", annotations={component.annotations}"
683
+ registration += ")"
666
684
  elif component_type == ComponentType.RESOURCE:
667
- registration += f'\nmcp.add_resource_fn(_wrapped_func, uri="{component.uri_template}", name="{component.name}", description="{component.docstring or ""}")'
685
+ registration += (
686
+ f"\nmcp.add_resource_fn(_wrapped_func, "
687
+ f'uri="{component.uri_template}", name="{component.name}", '
688
+ f'description="{component.docstring or ""}")'
689
+ )
668
690
  else: # PROMPT
669
- registration += f'\nmcp.add_prompt(_wrapped_func, name="{component.name}", description="{component.docstring or ""}")'
691
+ registration += (
692
+ f'\nmcp.add_prompt(_wrapped_func, name="{component.name}", '
693
+ f'description="{component.docstring or ""}")'
694
+ )
670
695
  else:
671
696
  # Standard registration without telemetry
672
697
  if component_type == ComponentType.TOOL:
@@ -689,6 +714,11 @@ class CodeGenerator:
689
714
  # Escape any quotes in the docstring
690
715
  escaped_docstring = component.docstring.replace('"', '\\"')
691
716
  registration += f', description="{escaped_docstring}"'
717
+
718
+ # Add annotations if present
719
+ if hasattr(component, "annotations") and component.annotations:
720
+ registration += f", annotations={component.annotations}"
721
+
692
722
  registration += ")"
693
723
 
694
724
  elif component_type == ComponentType.RESOURCE:
@@ -711,6 +741,7 @@ class CodeGenerator:
711
741
  # Escape any quotes in the docstring
712
742
  escaped_docstring = component.docstring.replace('"', '\\"')
713
743
  registration += f', description="{escaped_docstring}"'
744
+
714
745
  registration += ")"
715
746
 
716
747
  else: # PROMPT
@@ -735,6 +766,7 @@ class CodeGenerator:
735
766
  # Escape any quotes in the docstring
736
767
  escaped_docstring = component.docstring.replace('"', '\\"')
737
768
  registration += f', description="{escaped_docstring}"'
769
+
738
770
  registration += ")"
739
771
 
740
772
  component_registrations.append(registration)
@@ -770,6 +802,10 @@ class CodeGenerator:
770
802
  for key, value in auth_components["fastmcp_args"].items():
771
803
  mcp_constructor_args.append(f"{key}={value}")
772
804
 
805
+ # Add stateless HTTP parameter if enabled
806
+ if self.settings.stateless_http:
807
+ mcp_constructor_args.append("stateless_http=True")
808
+
773
809
  # Add OpenTelemetry parameters if enabled
774
810
  if self.settings.opentelemetry_enabled:
775
811
  mcp_constructor_args.append("lifespan=telemetry_lifespan")
@@ -778,6 +814,18 @@ class CodeGenerator:
778
814
  server_code_lines.append(mcp_instance_line)
779
815
  server_code_lines.append("")
780
816
 
817
+ # Add early telemetry initialization if enabled (before component registration)
818
+ early_telemetry_init = []
819
+ if self.settings.opentelemetry_enabled:
820
+ early_telemetry_init.extend(
821
+ [
822
+ "# Initialize telemetry early to ensure instrumentation works",
823
+ "from golf.telemetry.instrumentation import init_telemetry",
824
+ f'init_telemetry("{self.settings.name}")',
825
+ "",
826
+ ]
827
+ )
828
+
781
829
  # Main entry point with transport-specific app initialization
782
830
  main_code = [
783
831
  'if __name__ == "__main__":',
@@ -805,86 +853,115 @@ class CodeGenerator:
805
853
 
806
854
  # Transport-specific run methods
807
855
  if self.settings.transport == "sse":
808
- # Check if we need to add API key middleware for SSE
856
+ # Check if we need middleware for SSE
857
+ middleware_setup = []
858
+ middleware_list = []
859
+
809
860
  api_key_config = get_api_key_config()
810
861
  if auth_components.get("has_auth") and api_key_config:
811
- main_code.extend(
812
- [
813
- " # For SSE with API key auth, we need to get the app and add middleware",
814
- ' app = mcp.http_app(transport="sse")',
815
- " app.add_middleware(ApiKeyMiddleware)",
816
- ]
862
+ middleware_setup.append(
863
+ " from starlette.middleware import Middleware"
817
864
  )
818
- else:
865
+ middleware_list.append("Middleware(ApiKeyMiddleware)")
866
+
867
+ # Add OpenTelemetry middleware if enabled
868
+ if self.settings.opentelemetry_enabled:
869
+ middleware_setup.append(
870
+ " from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware"
871
+ )
872
+ middleware_setup.append(
873
+ " from starlette.middleware import Middleware"
874
+ )
875
+ middleware_list.append("Middleware(OpenTelemetryMiddleware)")
876
+
877
+ if middleware_setup:
878
+ main_code.extend(middleware_setup)
879
+ main_code.append(f" middleware = [{', '.join(middleware_list)}]")
880
+ main_code.append("")
819
881
  main_code.extend(
820
882
  [
821
- " # For SSE, get the app to add middleware",
822
- ' app = mcp.http_app(transport="sse")',
883
+ " # Run SSE server with middleware using FastMCP's run method",
884
+ ' mcp.run(transport="sse", host=host, port=port, log_level="info", middleware=middleware)',
823
885
  ]
824
886
  )
825
-
826
- # Add OpenTelemetry middleware to the SSE app if enabled
827
- if self.settings.opentelemetry_enabled:
887
+ else:
828
888
  main_code.extend(
829
889
  [
830
- " # Apply OpenTelemetry middleware to the SSE app",
831
- " from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware",
832
- " app = OpenTelemetryMiddleware(app)",
890
+ " # Run SSE server using FastMCP's run method",
891
+ ' mcp.run(transport="sse", host=host, port=port, log_level="info")',
833
892
  ]
834
893
  )
835
894
 
836
- main_code.extend(
837
- [
838
- " # Run with the configured app",
839
- ' uvicorn.run(app, host=host, port=port, log_level="info")',
840
- ]
841
- )
842
895
  elif self.settings.transport in ["streamable-http", "http"]:
843
- main_code.extend(
844
- [
845
- " # Create HTTP app and run with uvicorn",
846
- " app = mcp.http_app()",
847
- ]
848
- )
896
+ # Check if we need middleware for streamable-http
897
+ middleware_setup = []
898
+ middleware_list = []
849
899
 
850
- # Check if we need to add API key middleware
851
900
  api_key_config = get_api_key_config()
852
901
  if auth_components.get("has_auth") and api_key_config:
902
+ middleware_setup.append(
903
+ " from starlette.middleware import Middleware"
904
+ )
905
+ middleware_list.append("Middleware(ApiKeyMiddleware)")
906
+
907
+ # Add OpenTelemetry middleware if enabled
908
+ if self.settings.opentelemetry_enabled:
909
+ middleware_setup.append(
910
+ " from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware"
911
+ )
912
+ middleware_setup.append(
913
+ " from starlette.middleware import Middleware"
914
+ )
915
+ middleware_list.append("Middleware(OpenTelemetryMiddleware)")
916
+
917
+ if middleware_setup:
918
+ main_code.extend(middleware_setup)
919
+ main_code.append(f" middleware = [{', '.join(middleware_list)}]")
920
+ main_code.append("")
853
921
  main_code.extend(
854
922
  [
855
- " # Add API key middleware",
856
- " app.add_middleware(ApiKeyMiddleware)",
923
+ " # Run HTTP server with middleware using FastMCP's run method",
924
+ ' mcp.run(transport="streamable-http", host=host, port=port, log_level="info", middleware=middleware)',
857
925
  ]
858
926
  )
859
-
860
- # Add OpenTelemetry middleware to the HTTP app if enabled
861
- if self.settings.opentelemetry_enabled:
927
+ else:
862
928
  main_code.extend(
863
929
  [
864
- " # Apply OpenTelemetry middleware to the HTTP app",
865
- " from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware",
866
- " app = OpenTelemetryMiddleware(app)",
930
+ " # Run HTTP server using FastMCP's run method",
931
+ ' mcp.run(transport="streamable-http", host=host, port=port, log_level="info")',
867
932
  ]
868
933
  )
869
-
870
- main_code.extend(
871
- [' uvicorn.run(app, host=host, port=port, log_level="info")']
872
- )
873
934
  else:
874
935
  # For stdio transport, use mcp.run()
875
936
  main_code.extend(
876
937
  [" # Run with stdio transport", ' mcp.run(transport="stdio")']
877
938
  )
878
939
 
940
+ # Add health check route if enabled
941
+ health_check_code = []
942
+ if self.settings.health_check_enabled:
943
+ health_check_code = [
944
+ "# Add health check route",
945
+ "@mcp.custom_route('"
946
+ + self.settings.health_check_path
947
+ + '\', methods=["GET"])',
948
+ "async def health_check(request: Request) -> PlainTextResponse:",
949
+ ' """Health check endpoint for Kubernetes and load balancers."""',
950
+ f' return PlainTextResponse("{self.settings.health_check_response}")',
951
+ "",
952
+ ]
953
+
879
954
  # Combine all sections
880
955
  # Order: imports, env_section, auth_setup, server_code (mcp init),
881
- # post_init (API key middleware), component_registrations, main_code (run block)
956
+ # early_telemetry_init, component_registrations, health_check_code, main_code (run block)
882
957
  code = "\n".join(
883
958
  imports
884
959
  + env_section
885
960
  + auth_setup_code
886
961
  + server_code_lines
962
+ + early_telemetry_init
887
963
  + component_registrations
964
+ + health_check_code
888
965
  + main_code
889
966
  )
890
967
 
@@ -915,6 +992,21 @@ def build_project(
915
992
  build_env: Build environment ('dev' or 'prod')
916
993
  copy_env: Whether to copy environment variables to the built app
917
994
  """
995
+ # Load Golf credentials from .env for build operations (platform registration, etc.)
996
+ # This happens regardless of copy_env setting to ensure build process works
997
+ from dotenv import load_dotenv
998
+
999
+ project_env_file = project_path / ".env"
1000
+ if project_env_file.exists():
1001
+ # Load GOLF_* variables for build process
1002
+ load_dotenv(project_env_file, override=False)
1003
+
1004
+ # Only log if we actually found the specific Golf platform credentials
1005
+ has_api_key = "GOLF_API_KEY" in os.environ
1006
+ has_server_id = "GOLF_SERVER_ID" in os.environ
1007
+ if has_api_key and has_server_id:
1008
+ console.print("[dim]Loaded Golf credentials for build operations[/dim]")
1009
+
918
1010
  # Execute pre_build.py if it exists
919
1011
  pre_build_path = project_path / "pre_build.py"
920
1012
  if pre_build_path.exists():
@@ -950,10 +1042,11 @@ def build_project(
950
1042
  import traceback
951
1043
 
952
1044
  console.print(f"[red]{traceback.format_exc()}[/red]")
953
-
1045
+
954
1046
  # Track detailed error for pre_build.py execution failures
955
1047
  try:
956
1048
  from golf.core.telemetry import track_detailed_error
1049
+
957
1050
  track_detailed_error(
958
1051
  "build_pre_build_failed",
959
1052
  e,
@@ -962,7 +1055,7 @@ def build_project(
962
1055
  additional_props={
963
1056
  "file_path": str(pre_build_path.relative_to(project_path)),
964
1057
  "build_env": build_env,
965
- }
1058
+ },
966
1059
  )
967
1060
  except Exception:
968
1061
  # Don't let telemetry errors break the build
@@ -1015,9 +1108,6 @@ def build_project(
1015
1108
  env_vars_to_write["OTEL_TRACES_EXPORTER"] = (
1016
1109
  settings.opentelemetry_default_exporter
1017
1110
  )
1018
- console.print(
1019
- f"[info]Setting OTEL_TRACES_EXPORTER to '{settings.opentelemetry_default_exporter}' from golf.json in built app's .env[/info]"
1020
- )
1021
1111
 
1022
1112
  # 3. Apply Golf's project name as OTEL_SERVICE_NAME if not already set
1023
1113
  # (Ensures service name defaults to project name if not specified in user's .env)
@@ -1069,6 +1159,36 @@ def build_project(
1069
1159
  )
1070
1160
  generator.generate()
1071
1161
 
1162
+ # Platform registration (only for prod builds)
1163
+ if build_env == "prod":
1164
+ console.print(
1165
+ "[dim]Registering with Golf platform and updating resources...[/dim]"
1166
+ )
1167
+ import asyncio
1168
+
1169
+ try:
1170
+ from golf.core.platform import register_project_with_platform
1171
+
1172
+ asyncio.run(
1173
+ register_project_with_platform(
1174
+ project_path=project_path,
1175
+ settings=settings,
1176
+ components=generator.components,
1177
+ )
1178
+ )
1179
+ console.print("[green]✓ Platform registration completed[/green]")
1180
+ except ImportError:
1181
+ console.print(
1182
+ "[yellow]Warning: Platform registration module not available[/yellow]"
1183
+ )
1184
+ except Exception as e:
1185
+ console.print(
1186
+ f"[yellow]Warning: Platform registration failed: {e}[/yellow]"
1187
+ )
1188
+ console.print(
1189
+ "[yellow]Tip: Ensure GOLF_API_KEY and GOLF_SERVER_ID are available in your .env file[/yellow]"
1190
+ )
1191
+
1072
1192
  # Create a simple README
1073
1193
  readme_content = f"""# {settings.name}
1074
1194
 
@@ -1089,7 +1209,7 @@ This is a standalone FastMCP server generated by GolfMCP.
1089
1209
 
1090
1210
  # Copy pyproject.toml with required dependencies
1091
1211
  base_dependencies = [
1092
- "fastmcp>=2.0.0",
1212
+ "fastmcp>=2.0.0,<2.6.0",
1093
1213
  "uvicorn>=0.20.0",
1094
1214
  "pydantic>=2.0.0",
1095
1215
  "python-dotenv>=1.0.0",
golf/core/config.py CHANGED
@@ -102,6 +102,19 @@ class Settings(BaseSettings):
102
102
  "console", description="Default OpenTelemetry exporter type"
103
103
  )
104
104
 
105
+ # Health check configuration
106
+ health_check_enabled: bool = Field(
107
+ False, description="Enable health check endpoint"
108
+ )
109
+ health_check_path: str = Field("/health", description="Health check endpoint path")
110
+ health_check_response: str = Field("OK", description="Health check response text")
111
+
112
+ # HTTP session behaviour
113
+ stateless_http: bool = Field(
114
+ False,
115
+ description="Make Streamable-HTTP transport stateless (new session per request)",
116
+ )
117
+
105
118
 
106
119
  def find_config_path(start_path: Path | None = None) -> Path | None:
107
120
  """Find the golf config file by searching upwards from the given path.