golf-mcp 0.2.9__py3-none-any.whl → 0.2.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,4 +1,4 @@
1
- __version__ = "0.2.9"
1
+ __version__ = "0.2.11"
2
2
 
3
3
  # Import endpoints with fallback for dev mode
4
4
  try:
golf/core/builder.py CHANGED
@@ -581,6 +581,201 @@ class CodeGenerator:
581
581
  # Default to older behavior for safety
582
582
  return False
583
583
 
584
+ def _generate_startup_section(self, project_path: Path) -> list[str]:
585
+ """Generate code section for startup.py execution during server runtime."""
586
+ startup_path = project_path / "startup.py"
587
+
588
+ if not startup_path.exists():
589
+ return []
590
+
591
+ return [
592
+ "",
593
+ "# Execute startup script for loading secrets and initialization",
594
+ "import importlib.util",
595
+ "import sys",
596
+ "import os",
597
+ "from pathlib import Path",
598
+ "",
599
+ "# Look for startup.py in the same directory as this server.py",
600
+ "startup_path = Path(__file__).parent / 'startup.py'",
601
+ "if startup_path.exists():",
602
+ " try:",
603
+ " # Save original environment for restoration",
604
+ " try:",
605
+ " original_dir = os.getcwd()",
606
+ " except (FileNotFoundError, OSError):",
607
+ " # Use server directory as fallback",
608
+ " original_dir = str(Path(__file__).parent)",
609
+ " os.chdir(original_dir)",
610
+ " original_path = sys.path.copy()",
611
+ " ",
612
+ " # Set context for startup script execution",
613
+ " script_dir = str(startup_path.parent)",
614
+ " os.chdir(script_dir)",
615
+ " sys.path.insert(0, script_dir)",
616
+ " ",
617
+ " # Debug output for startup script development",
618
+ " if os.environ.get('GOLF_DEBUG'):",
619
+ " print(f'Executing startup script: {startup_path}')",
620
+ " print(f'Working directory: {os.getcwd()}')",
621
+ " print(f'Python path: {sys.path[:3]}...')", # Show first 3 entries
622
+ " ",
623
+ " # Load and execute startup script",
624
+ " spec = importlib.util.spec_from_file_location('startup', startup_path)",
625
+ " if spec and spec.loader:",
626
+ " startup_module = importlib.util.module_from_spec(spec)",
627
+ " spec.loader.exec_module(startup_module)",
628
+ " else:",
629
+ " print('Warning: Could not load startup.py', file=sys.stderr)",
630
+ " ",
631
+ " except Exception as e:",
632
+ " import traceback",
633
+ " print(f'Warning: Startup script execution failed: {e}', file=sys.stderr)",
634
+ " print(traceback.format_exc(), file=sys.stderr)",
635
+ " # Continue server startup despite script failure",
636
+ " ",
637
+ " finally:",
638
+ " # Always restore original environment",
639
+ " try:",
640
+ " os.chdir(original_dir)",
641
+ " sys.path[:] = original_path",
642
+ " except Exception:",
643
+ " # If directory restoration fails, at least fix the path",
644
+ " sys.path[:] = original_path",
645
+ "",
646
+ ]
647
+
648
+ def _generate_readiness_section(self, project_path: Path) -> list[str]:
649
+ """Generate code section for readiness.py execution during server runtime."""
650
+ readiness_path = project_path / "readiness.py"
651
+
652
+ if not readiness_path.exists():
653
+ # Only generate default readiness if health checks are explicitly enabled
654
+ if not self.settings.health_check_enabled:
655
+ return []
656
+ return [
657
+ "# Default readiness check - no custom readiness.py found",
658
+ "@mcp.custom_route('/ready', methods=[\"GET\"])",
659
+ "async def readiness_check(request: Request) -> JSONResponse:",
660
+ ' """Readiness check endpoint for Kubernetes and load balancers."""',
661
+ ' return JSONResponse({"status": "pass"}, status_code=200)',
662
+ "",
663
+ ]
664
+
665
+ return [
666
+ "# Custom readiness check from readiness.py",
667
+ "from readiness import check as readiness_check_func",
668
+ "@mcp.custom_route('/ready', methods=[\"GET\"])",
669
+ "async def readiness_check(request: Request):",
670
+ ' """Readiness check endpoint for Kubernetes and load balancers."""',
671
+ " result = readiness_check_func()",
672
+ " if isinstance(result, dict):",
673
+ " return JSONResponse(result)",
674
+ " return result",
675
+ "",
676
+ ]
677
+
678
+ def _generate_health_section(self, project_path: Path) -> list[str]:
679
+ """Generate code section for health.py execution during server runtime."""
680
+ health_path = project_path / "health.py"
681
+
682
+ if not health_path.exists():
683
+ # Check if legacy health configuration is used
684
+ if self.settings.health_check_enabled:
685
+ return [
686
+ "# Legacy health check configuration (deprecated)",
687
+ "@mcp.custom_route('" + self.settings.health_check_path + '\', methods=["GET"])',
688
+ "async def health_check(request: Request) -> PlainTextResponse:",
689
+ ' """Health check endpoint for Kubernetes and load balancers."""',
690
+ f' return PlainTextResponse("{self.settings.health_check_response}")',
691
+ "",
692
+ ]
693
+ else:
694
+ # If health checks are disabled, return empty (no default health check)
695
+ return []
696
+
697
+ return [
698
+ "# Custom health check from health.py",
699
+ "from health import check as health_check_func",
700
+ "@mcp.custom_route('/health', methods=[\"GET\"])",
701
+ "async def health_check(request: Request):",
702
+ ' """Health check endpoint for Kubernetes and load balancers."""',
703
+ " result = health_check_func()",
704
+ " if isinstance(result, dict):",
705
+ " return JSONResponse(result)",
706
+ " return result",
707
+ "",
708
+ ]
709
+
710
+ def _generate_check_function_helper(self) -> list[str]:
711
+ """Generate helper function to call custom check functions."""
712
+ return [
713
+ "# Helper function to call custom check functions",
714
+ "async def _call_check_function(check_type: str) -> JSONResponse:",
715
+ ' """Call custom check function and handle errors gracefully."""',
716
+ " import importlib.util",
717
+ " import traceback",
718
+ " from pathlib import Path",
719
+ " from datetime import datetime",
720
+ " ",
721
+ " try:",
722
+ " # Load the custom check module",
723
+ " module_path = Path(__file__).parent / f'{check_type}.py'",
724
+ " if not module_path.exists():",
725
+ ' return JSONResponse({"status": "pass"}, status_code=200)',
726
+ " ",
727
+ " spec = importlib.util.spec_from_file_location(f'{check_type}_check', module_path)",
728
+ " if spec and spec.loader:",
729
+ " module = importlib.util.module_from_spec(spec)",
730
+ " spec.loader.exec_module(module)",
731
+ " ",
732
+ " # Call the check function if it exists",
733
+ " if hasattr(module, 'check'):",
734
+ " result = module.check()",
735
+ " ",
736
+ " # Handle different return types",
737
+ " if isinstance(result, dict):",
738
+ " # User returned structured response",
739
+ " status_code = result.get('status_code', 200)",
740
+ " response_data = {k: v for k, v in result.items() if k != 'status_code'}",
741
+ " elif isinstance(result, bool):",
742
+ " # User returned simple boolean",
743
+ " status_code = 200 if result else 503",
744
+ " response_data = {",
745
+ ' "status": "pass" if result else "fail",',
746
+ ' "timestamp": datetime.utcnow().isoformat()',
747
+ " }",
748
+ " elif result is None:",
749
+ " # User returned nothing - assume success",
750
+ " status_code = 200",
751
+ ' response_data = {"status": "pass"}',
752
+ " else:",
753
+ " # User returned something else - treat as success message",
754
+ " status_code = 200",
755
+ " response_data = {",
756
+ ' "status": "pass",',
757
+ ' "message": str(result)',
758
+ " }",
759
+ " ",
760
+ " return JSONResponse(response_data, status_code=status_code)",
761
+ " else:",
762
+ " return JSONResponse(",
763
+ ' {"status": "fail", "error": f"No check() function found in {check_type}.py"},',
764
+ " status_code=503",
765
+ " )",
766
+ " ",
767
+ " except Exception as e:",
768
+ " # Log error and return failure response",
769
+ " import sys",
770
+ ' print(f"Error calling {check_type} check function: {e}", file=sys.stderr)',
771
+ " print(traceback.format_exc(), file=sys.stderr)",
772
+ " return JSONResponse({",
773
+ ' "status": "fail",',
774
+ ' "error": f"Error calling {check_type} check function: {str(e)}"',
775
+ " }, status_code=503)",
776
+ "",
777
+ ]
778
+
584
779
  def _generate_server(self) -> None:
585
780
  """Generate the main server entry point."""
586
781
  server_file = self.output_dir / "server.py"
@@ -636,14 +831,31 @@ class CodeGenerator:
636
831
  imports.extend(generate_metrics_instrumentation())
637
832
  imports.extend(generate_session_tracking())
638
833
 
639
- # Add health check imports if enabled
640
- if self.settings.health_check_enabled:
641
- imports.extend(
642
- [
643
- "from starlette.requests import Request",
644
- "from starlette.responses import PlainTextResponse",
645
- ]
646
- )
834
+ # Add health check imports only when we generate default endpoints
835
+ readiness_exists = (self.project_path / "readiness.py").exists()
836
+ health_exists = (self.project_path / "health.py").exists()
837
+
838
+ # Only import starlette when we generate default endpoints (not when custom files exist)
839
+ will_generate_default_readiness = not readiness_exists and self.settings.health_check_enabled
840
+ will_generate_default_health = not health_exists and self.settings.health_check_enabled
841
+
842
+ if will_generate_default_readiness or will_generate_default_health:
843
+ imports.append("from starlette.requests import Request")
844
+
845
+ # Determine response types needed for default endpoints
846
+ response_types = []
847
+ if will_generate_default_readiness:
848
+ response_types.append("JSONResponse")
849
+ if will_generate_default_health:
850
+ response_types.append("PlainTextResponse")
851
+
852
+ if response_types:
853
+ imports.append(f"from starlette.responses import {', '.join(response_types)}")
854
+
855
+ # Import Request and JSONResponse for custom check routes (they need both)
856
+ elif readiness_exists or health_exists:
857
+ imports.append("from starlette.requests import Request")
858
+ imports.append("from starlette.responses import JSONResponse")
647
859
 
648
860
  # Get transport-specific configuration
649
861
  transport_config = self._get_transport_config(self.settings.transport)
@@ -935,6 +1147,9 @@ class CodeGenerator:
935
1147
  "",
936
1148
  ]
937
1149
 
1150
+ # Generate startup section
1151
+ startup_section = self._generate_startup_section(self.project_path)
1152
+
938
1153
  # OpenTelemetry setup code will be handled through imports and lifespan
939
1154
 
940
1155
  # Add auth setup code if auth is configured
@@ -1132,32 +1347,30 @@ class CodeGenerator:
1132
1347
 
1133
1348
  metrics_route_code = generate_metrics_route(self.settings.metrics_path)
1134
1349
 
1135
- # Add health check route if enabled
1136
- health_check_code = []
1137
- if self.settings.health_check_enabled:
1138
- health_check_code = [
1139
- "# Add health check route",
1140
- "@mcp.custom_route('" + self.settings.health_check_path + '\', methods=["GET"])',
1141
- "async def health_check(request: Request) -> PlainTextResponse:",
1142
- ' """Health check endpoint for Kubernetes and load balancers."""',
1143
- (f' return PlainTextResponse("{self.settings.health_check_response}")'),
1144
- "",
1145
- ]
1350
+ # Generate readiness and health check sections
1351
+ readiness_section = self._generate_readiness_section(self.project_path)
1352
+ health_section = self._generate_health_section(self.project_path)
1353
+
1354
+ # No longer need the check helper function since we use direct imports
1355
+ check_helper_section = []
1146
1356
 
1147
1357
  # Combine all sections
1148
- # Order: imports, env_section, auth_setup, server_code (mcp init),
1358
+ # Order: imports, env_section, startup_section, auth_setup, server_code (mcp init),
1149
1359
  # early_telemetry_init, early_metrics_init, component_registrations,
1150
- # metrics_route_code, health_check_code, main_code (run block)
1360
+ # metrics_route_code, check_helper_section, readiness_section, health_section, main_code (run block)
1151
1361
  code = "\n".join(
1152
1362
  imports
1153
1363
  + env_section
1364
+ + startup_section
1154
1365
  + auth_setup_code
1155
1366
  + server_code_lines
1156
1367
  + early_telemetry_init
1157
1368
  + early_metrics_init
1158
1369
  + component_registrations
1159
1370
  + metrics_route_code
1160
- + health_check_code
1371
+ + check_helper_section
1372
+ + readiness_section
1373
+ + health_section
1161
1374
  + main_code
1162
1375
  )
1163
1376
 
@@ -1352,6 +1565,24 @@ def build_project(
1352
1565
  generator = CodeGenerator(project_path, settings, output_dir, build_env=build_env, copy_env=copy_env)
1353
1566
  generator.generate()
1354
1567
 
1568
+ # Copy startup.py to output directory if it exists (after server generation)
1569
+ startup_path = project_path / "startup.py"
1570
+ if startup_path.exists():
1571
+ dest_path = output_dir / "startup.py"
1572
+ shutil.copy2(startup_path, dest_path)
1573
+ console.print(get_status_text("success", "Startup script copied to build directory"))
1574
+
1575
+ # Copy optional check files to build directory
1576
+ readiness_path = project_path / "readiness.py"
1577
+ if readiness_path.exists():
1578
+ shutil.copy2(readiness_path, output_dir)
1579
+ console.print(get_status_text("success", "Readiness script copied to build directory"))
1580
+
1581
+ health_path = project_path / "health.py"
1582
+ if health_path.exists():
1583
+ shutil.copy2(health_path, output_dir)
1584
+ console.print(get_status_text("success", "Health script copied to build directory"))
1585
+
1355
1586
  # Platform registration (only for prod builds)
1356
1587
  if build_env == "prod":
1357
1588
  console.print()
golf/core/config.py CHANGED
@@ -83,9 +83,9 @@ class Settings(BaseSettings):
83
83
  )
84
84
 
85
85
  # Health check configuration
86
- health_check_enabled: bool = Field(False, description="Enable health check endpoint")
86
+ health_check_enabled: bool = Field(False, description="Enable health check endpoint (deprecated - use health.py)")
87
87
  health_check_path: str = Field("/health", description="Health check endpoint path")
88
- health_check_response: str = Field("OK", description="Health check response text")
88
+ health_check_response: str = Field("OK", description="Health check response text (deprecated - use health.py)")
89
89
 
90
90
  # HTTP session behaviour
91
91
  stateless_http: bool = Field(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.2.9
3
+ Version: 0.2.11
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -1,4 +1,4 @@
1
- golf/__init__.py,sha256=CB9wu2mcYCSJopA-uO1-_87ebOSTDcXJ8uOktQ4va20,306
1
+ golf/__init__.py,sha256=OpQ-bTTwRbO4KTjcnQgvU8ccnCWbcM-xPCnqQkEJNz0,307
2
2
  golf/_endpoints.py,sha256=-mvAmjx3YtqfAdajO13Kv7LKVBMdqsKqX0_3aCmCK4I,317
3
3
  golf/_endpoints.py.in,sha256=MeqcSF6xinnPknpBGF26xA9FYmSYKSWqCFXOSDlXiOA,216
4
4
  golf/_endpoints_fallback.py,sha256=CD6m45Ams1A9HVKowY8dCFUDMiFmJ8ZWSwHCENvU5u4,386
@@ -16,11 +16,11 @@ golf/commands/build.py,sha256=sLq9lSW4naq2vIlBreKI5SGnviQrhBWw13zfOZOKhuM,2293
16
16
  golf/commands/init.py,sha256=KkAg_3-KxBDFOcZqUHqcPAkDipykFVaLWpQ2tydnVPk,9617
17
17
  golf/commands/run.py,sha256=a8GSwLt6E2cUJespv-u3jbD-rNUMHqF3VArD1uXV-Vk,4299
18
18
  golf/core/__init__.py,sha256=4bKeskJ2fPaZqkz2xQScSa3phRLLrmrczwSL632jv-o,52
19
- golf/core/builder.py,sha256=Tx6AtWnnasY_Rfu1CEdhE7y3hZdN0RuhwLevJU9vZgo,66355
19
+ golf/core/builder.py,sha256=c0Phx77jkEJnks-FGN7ONuBmC5tQsVIHbwJxQkDAjKE,78180
20
20
  golf/core/builder_auth.py,sha256=SXPCpc5ipntoNqIAIA2ZCeGmEua6QVs7yC3MDtGKAro,8224
21
21
  golf/core/builder_metrics.py,sha256=j6Gtgd867o46JbDfSNGNsHt1QtV1XHKUJs1z8r4siQM,8830
22
22
  golf/core/builder_telemetry.py,sha256=86bp7UlMUN6JyQRrZ5EizovP6AJ_q65OANJTeJXDIKc,3421
23
- golf/core/config.py,sha256=h8LWKdD-5vS8vRGs1qsCI2d8EbtjIKuXM3rUJ0T7EZ8,7452
23
+ golf/core/config.py,sha256=BD4bfyzIv1TZYDmBQTuzSXNF6aHfzWXY65LemAxdjNo,7510
24
24
  golf/core/parser.py,sha256=f9WqmLWlFuXQCObl2Qmna9bp_Bo0p0eIlukzwFaBSzo,43373
25
25
  golf/core/platform.py,sha256=0TxLfVPBhBR82aJAQWEJcnkGuQv65C9gYYy7P2foUVk,6662
26
26
  golf/core/telemetry.py,sha256=dXoWrgrQpj_HGrl_8TBZmRnuLxFKEn0GSDWQ9qq3ZQM,15686
@@ -48,9 +48,9 @@ golf/utilities/__init__.py,sha256=X9iY9yi3agz1GVcn8-qWeOCt8CSSsruHxqPNtiF63TY,53
48
48
  golf/utilities/context.py,sha256=DGGvhVe---QMhy0wtdWhNp-_WVk1NvAcOFn0uBKBpYo,1579
49
49
  golf/utilities/elicitation.py,sha256=MParZZZsY45s70-KXduHa6IvpWXnLW2FCPfrGijMaHs,5223
50
50
  golf/utilities/sampling.py,sha256=88nDv-trBE4gZQbcnMjXl3LW6TiIhv5zR_cuEIGjaIM,7233
51
- golf_mcp-0.2.9.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
52
- golf_mcp-0.2.9.dist-info/METADATA,sha256=67-svZP_no-mw7BBDGwoQJJ_RCcqCQH5uEAk7jfqUF0,9370
53
- golf_mcp-0.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
- golf_mcp-0.2.9.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
55
- golf_mcp-0.2.9.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
56
- golf_mcp-0.2.9.dist-info/RECORD,,
51
+ golf_mcp-0.2.11.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
52
+ golf_mcp-0.2.11.dist-info/METADATA,sha256=DDudTuflPauNfgHynFhldQLFI7MlJ-leZBZBhAdZpuY,9371
53
+ golf_mcp-0.2.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ golf_mcp-0.2.11.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
55
+ golf_mcp-0.2.11.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
56
+ golf_mcp-0.2.11.dist-info/RECORD,,