golf-mcp 0.2.10__py3-none-any.whl → 0.2.12__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,9 +1 @@
1
- __version__ = "0.2.10"
2
-
3
- # Import endpoints with fallback for dev mode
4
- try:
5
- # In built wheels, this exists (generated from _endpoints.py.in)
6
- from . import _endpoints
7
- except ImportError:
8
- # In editable/dev installs, fall back to env-based values
9
- from . import _endpoints_fallback as _endpoints
1
+ __version__ = "0.2.12"
golf/auth/__init__.py CHANGED
@@ -32,11 +32,9 @@ from .registry import (
32
32
  # Re-export for backward compatibility
33
33
  from .api_key import configure_api_key, get_api_key_config, is_api_key_configured
34
34
  from .helpers import (
35
- debug_api_key_context,
36
35
  extract_token_from_header,
37
36
  get_api_key,
38
37
  get_auth_token,
39
- get_provider_token,
40
38
  set_api_key,
41
39
  )
42
40
 
@@ -70,11 +68,9 @@ __all__ = [
70
68
  "get_api_key_config",
71
69
  "is_api_key_configured",
72
70
  # Helper functions
73
- "debug_api_key_context",
74
71
  "extract_token_from_header",
75
72
  "get_api_key",
76
73
  "get_auth_token",
77
- "get_provider_token",
78
74
  "set_api_key",
79
75
  ]
80
76
 
golf/auth/helpers.py CHANGED
@@ -1,26 +1,12 @@
1
1
  """Helper functions for working with authentication in MCP context."""
2
2
 
3
3
  from contextvars import ContextVar
4
- from typing import Any
5
4
 
6
- from starlette.requests import Request
7
5
 
8
6
  # Context variable to store the current request's API key
9
7
  _current_api_key: ContextVar[str | None] = ContextVar("current_api_key", default=None)
10
8
 
11
9
 
12
- def get_provider_token() -> str | None:
13
- """
14
- Get a provider token (legacy function - no longer supported in Golf 0.2.x).
15
-
16
- In Golf 0.2.x, use FastMCP's built-in auth providers for OAuth flows.
17
- This function returns None and is kept for backwards compatibility.
18
- """
19
- # Legacy OAuth provider support removed in Golf 0.2.x
20
- # Use FastMCP 2.11+ auth providers instead
21
- return None
22
-
23
-
24
10
  def extract_token_from_header(auth_header: str) -> str | None:
25
11
  """Extract bearer token from Authorization header.
26
12
 
@@ -123,25 +109,6 @@ def get_api_key() -> str | None:
123
109
  return None
124
110
 
125
111
 
126
- def get_api_key_from_request(request: Request) -> str | None:
127
- """Get the API key from a specific request object.
128
-
129
- This is useful when you have direct access to the request object.
130
-
131
- Args:
132
- request: The Starlette Request object
133
-
134
- Returns:
135
- The API key if available, None otherwise
136
- """
137
- # Check request state first (set by our middleware)
138
- if hasattr(request, "state") and hasattr(request.state, "api_key"):
139
- return request.state.api_key
140
-
141
- # Fall back to context variable
142
- return _current_api_key.get()
143
-
144
-
145
112
  def get_auth_token() -> str | None:
146
113
  """Get the authorization token from the current request context.
147
114
 
@@ -206,52 +173,3 @@ def get_auth_token() -> str | None:
206
173
  pass
207
174
 
208
175
  return None
209
-
210
-
211
- def debug_api_key_context() -> dict[str, Any]:
212
- """Debug function to inspect API key context.
213
-
214
- Returns a dictionary with debugging information about the current
215
- API key context. Useful for troubleshooting authentication issues.
216
-
217
- Returns:
218
- Dictionary with debug information
219
- """
220
- import asyncio
221
- import os
222
- import sys
223
-
224
- debug_info = {
225
- "context_var_value": _current_api_key.get(),
226
- "has_async_task": False,
227
- "task_id": None,
228
- "main_module_has_storage": False,
229
- "main_module_has_context": False,
230
- "request_id_from_context": None,
231
- "env_vars": {
232
- "API_KEY": bool(os.environ.get("API_KEY")),
233
- "GOLF_API_KEY_DEBUG": os.environ.get("GOLF_API_KEY_DEBUG", "false"),
234
- },
235
- }
236
-
237
- try:
238
- task = asyncio.current_task()
239
- if task:
240
- debug_info["has_async_task"] = True
241
- debug_info["task_id"] = id(task)
242
- except Exception:
243
- pass
244
-
245
- try:
246
- main_module = sys.modules.get("__main__")
247
- if main_module:
248
- debug_info["main_module_has_storage"] = hasattr(main_module, "api_key_storage")
249
- debug_info["main_module_has_context"] = hasattr(main_module, "request_id_context")
250
-
251
- if hasattr(main_module, "request_id_context"):
252
- request_id_context = main_module.request_id_context
253
- debug_info["request_id_from_context"] = request_id_context.get()
254
- except Exception:
255
- pass
256
-
257
- return debug_info
golf/cli/branding.py CHANGED
@@ -33,7 +33,6 @@ STATUS_ICONS = {
33
33
  "building": "🔨",
34
34
  "generating": "⚙️",
35
35
  "packaging": "📦",
36
- "platform": "⛳",
37
36
  "server": "🚀",
38
37
  "loading": "⭕",
39
38
  }
@@ -163,7 +162,7 @@ def get_status_text(status: str, message: str, style: str = "") -> Text:
163
162
  elif status == "warning":
164
163
  text.append("⚡ ", style=f"bold {GOLF_ORANGE}")
165
164
  text.append(f"{icon} {message}", style=f"bold {GOLF_ORANGE}")
166
- elif status in ["building", "generating", "packaging", "platform"]:
165
+ elif status in ["building", "generating", "packaging"]:
167
166
  text.append("🔥 ", style=f"bold {GOLF_ORANGE}")
168
167
  text.append(f"{icon} {message}", style=f"bold {GOLF_BLUE}")
169
168
  else:
golf/cli/main.py CHANGED
@@ -167,11 +167,9 @@ def build_prod(
167
167
  ) -> None:
168
168
  """Build a production version for deployment.
169
169
 
170
- Golf credentials (GOLF_*) are always loaded from .env for build operations
171
- (platform registration, resource updates). App environment variables are
172
- NOT copied for security - provide them in your deployment environment.
173
-
174
- Your production deployment must include GOLF_* vars for runtime telemetry.
170
+ Environment variables from .env are loaded for build operations.
171
+ App environment variables are NOT copied for security - provide them
172
+ in your deployment environment.
175
173
  """
176
174
  # Find project root directory
177
175
  project_root, config_path = find_project_root()
golf/commands/run.py CHANGED
@@ -5,7 +5,7 @@ import subprocess
5
5
  import sys
6
6
  from pathlib import Path
7
7
 
8
- from rich.console import Console
8
+ from rich.console import Console, Group
9
9
  from rich.panel import Panel
10
10
  from rich.align import Align
11
11
  from rich.text import Text
@@ -53,23 +53,35 @@ def run_server(
53
53
  server_host = host or settings.host or "localhost"
54
54
  server_port = port or settings.port or 3000
55
55
 
56
- server_content = Text()
57
- server_content.append("🚀 ", style=f"bold {GOLF_ORANGE}")
58
- server_content.append(f"{STATUS_ICONS['server']} Server starting on ", style=f"bold {GOLF_BLUE}")
59
- server_content.append(f"http://{server_host}:{server_port}", style=f"bold {GOLF_GREEN}")
60
- server_content.append(" 🚀", style=f"bold {GOLF_ORANGE}")
61
- server_content.append("\n")
56
+ # Create server URL line
57
+ server_line = Text()
58
+ server_line.append("🚀 ", style=f"bold {GOLF_ORANGE}")
59
+ server_line.append(f"{STATUS_ICONS['server']} Server starting on ", style=f"bold {GOLF_BLUE}")
60
+ server_line.append(f"http://{server_host}:{server_port}", style=f"bold {GOLF_GREEN}")
61
+
62
+ # Create content with proper alignment
63
+ content_lines = [
64
+ "", # Empty line at top
65
+ Align.center(server_line),
66
+ ]
62
67
 
63
68
  # Add telemetry status indicator
64
69
  if settings.opentelemetry_enabled:
65
- server_content.append("📊 Golf telemetry enabled", style=f"dim {GOLF_BLUE}")
66
- server_content.append("\n")
67
-
68
- server_content.append("⚡ Press Ctrl+C to stop ⚡", style=f"dim {GOLF_ORANGE}")
70
+ telemetry_line = Text("📊 OpenTelemetry enabled", style=f"dim {GOLF_BLUE}")
71
+ content_lines.append(Align.center(telemetry_line))
72
+
73
+ # Add empty line and stop instruction
74
+ content_lines.extend(
75
+ [
76
+ "", # Empty line before stop instruction
77
+ Align.center(Text("⚡ Press Ctrl+C to stop ⚡", style=f"dim {GOLF_ORANGE}")),
78
+ "", # Empty line at bottom
79
+ ]
80
+ )
69
81
 
70
82
  console.print(
71
83
  Panel(
72
- Align.center(server_content),
84
+ Group(*content_lines),
73
85
  border_style=GOLF_BLUE,
74
86
  padding=(1, 2),
75
87
  title="[bold]🌐 SERVER READY 🌐[/bold]",
golf/core/builder.py CHANGED
@@ -16,7 +16,7 @@ from golf.core.builder_auth import generate_auth_code, generate_auth_routes
16
16
  from golf.core.builder_telemetry import (
17
17
  generate_telemetry_imports,
18
18
  )
19
- from golf.cli.branding import create_build_header, get_status_text, STATUS_ICONS, GOLF_BLUE
19
+ from golf.cli.branding import create_build_header, get_status_text
20
20
  from golf.core.config import Settings
21
21
  from golf.core.parser import (
22
22
  ComponentType,
@@ -645,6 +645,137 @@ class CodeGenerator:
645
645
  "",
646
646
  ]
647
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
+
648
779
  def _generate_server(self) -> None:
649
780
  """Generate the main server entry point."""
650
781
  server_file = self.output_dir / "server.py"
@@ -700,14 +831,31 @@ class CodeGenerator:
700
831
  imports.extend(generate_metrics_instrumentation())
701
832
  imports.extend(generate_session_tracking())
702
833
 
703
- # Add health check imports if enabled
704
- if self.settings.health_check_enabled:
705
- imports.extend(
706
- [
707
- "from starlette.requests import Request",
708
- "from starlette.responses import PlainTextResponse",
709
- ]
710
- )
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")
711
859
 
712
860
  # Get transport-specific configuration
713
861
  transport_config = self._get_transport_config(self.settings.transport)
@@ -1199,22 +1347,17 @@ class CodeGenerator:
1199
1347
 
1200
1348
  metrics_route_code = generate_metrics_route(self.settings.metrics_path)
1201
1349
 
1202
- # Add health check route if enabled
1203
- health_check_code = []
1204
- if self.settings.health_check_enabled:
1205
- health_check_code = [
1206
- "# Add health check route",
1207
- "@mcp.custom_route('" + self.settings.health_check_path + '\', methods=["GET"])',
1208
- "async def health_check(request: Request) -> PlainTextResponse:",
1209
- ' """Health check endpoint for Kubernetes and load balancers."""',
1210
- (f' return PlainTextResponse("{self.settings.health_check_response}")'),
1211
- "",
1212
- ]
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 = []
1213
1356
 
1214
1357
  # Combine all sections
1215
1358
  # Order: imports, env_section, startup_section, auth_setup, server_code (mcp init),
1216
1359
  # early_telemetry_init, early_metrics_init, component_registrations,
1217
- # 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)
1218
1361
  code = "\n".join(
1219
1362
  imports
1220
1363
  + env_section
@@ -1225,7 +1368,9 @@ class CodeGenerator:
1225
1368
  + early_metrics_init
1226
1369
  + component_registrations
1227
1370
  + metrics_route_code
1228
- + health_check_code
1371
+ + check_helper_section
1372
+ + readiness_section
1373
+ + health_section
1229
1374
  + main_code
1230
1375
  )
1231
1376
 
@@ -1256,21 +1401,13 @@ def build_project(
1256
1401
  build_env: Build environment ('dev' or 'prod')
1257
1402
  copy_env: Whether to copy environment variables to the built app
1258
1403
  """
1259
- # Load Golf credentials from .env for build operations (platform registration, etc.)
1260
- # This happens regardless of copy_env setting to ensure build process works
1404
+ # Load environment variables from .env for build operations
1261
1405
  from dotenv import load_dotenv
1262
1406
 
1263
1407
  project_env_file = project_path / ".env"
1264
1408
  if project_env_file.exists():
1265
- # Load GOLF_* variables for build process
1266
1409
  load_dotenv(project_env_file, override=False)
1267
1410
 
1268
- # Only log if we actually found the specific Golf platform credentials
1269
- has_api_key = "GOLF_API_KEY" in os.environ
1270
- has_server_id = "GOLF_SERVER_ID" in os.environ
1271
- if has_api_key and has_server_id:
1272
- console.print("[dim]Loaded Golf credentials for build operations[/dim]")
1273
-
1274
1411
  # Execute auth.py if it exists (for authentication configuration)
1275
1412
  # Also support legacy pre_build.py for backward compatibility
1276
1413
  auth_path = project_path / "auth.py"
@@ -1427,32 +1564,16 @@ def build_project(
1427
1564
  shutil.copy2(startup_path, dest_path)
1428
1565
  console.print(get_status_text("success", "Startup script copied to build directory"))
1429
1566
 
1430
- # Platform registration (only for prod builds)
1431
- if build_env == "prod":
1432
- console.print()
1433
- status_msg = f"[{GOLF_BLUE}]{STATUS_ICONS['platform']} Registering with Golf platform and updating resources...[/{GOLF_BLUE}]"
1434
- with console.status(status_msg):
1435
- import asyncio
1436
-
1437
- try:
1438
- from golf.core.platform import register_project_with_platform
1567
+ # Copy optional check files to build directory
1568
+ readiness_path = project_path / "readiness.py"
1569
+ if readiness_path.exists():
1570
+ shutil.copy2(readiness_path, output_dir)
1571
+ console.print(get_status_text("success", "Readiness script copied to build directory"))
1439
1572
 
1440
- success = asyncio.run(
1441
- register_project_with_platform(
1442
- project_path=project_path,
1443
- settings=settings,
1444
- components=generator.components,
1445
- )
1446
- )
1447
-
1448
- if success:
1449
- console.print(get_status_text("success", "Platform registration completed"))
1450
- # If success is False, the platform module already printed appropriate warnings
1451
- except ImportError:
1452
- console.print(get_status_text("warning", "Platform registration module not available"))
1453
- except Exception as e:
1454
- console.print(get_status_text("warning", f"Platform registration failed: {e}"))
1455
- console.print("[dim]Tip: Ensure GOLF_API_KEY and GOLF_SERVER_ID are available in your .env file[/dim]")
1573
+ health_path = project_path / "health.py"
1574
+ if health_path.exists():
1575
+ shutil.copy2(health_path, output_dir)
1576
+ console.print(get_status_text("success", "Health script copied to build directory"))
1456
1577
 
1457
1578
  # Create a simple README
1458
1579
  readme_content = f"""# {settings.name}
@@ -1483,7 +1604,7 @@ This is a standalone FastMCP server generated by GolfMCP.
1483
1604
 
1484
1605
  # Legacy ProviderConfig removed in Golf 0.2.x - use modern auth configurations
1485
1606
  # Legacy OAuth imports removed in Golf 0.2.x - use FastMCP 2.11+ auth providers
1486
- from golf.auth.helpers import get_provider_token, extract_token_from_header, get_api_key, set_api_key
1607
+ from golf.auth.helpers import extract_token_from_header, get_api_key, set_api_key
1487
1608
  from golf.auth.api_key import configure_api_key, get_api_key_config
1488
1609
  from golf.auth.factory import create_auth_provider
1489
1610
  from golf.auth.providers import RemoteAuthConfig, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig
golf/core/builder_auth.py CHANGED
@@ -121,7 +121,7 @@ def generate_api_key_auth_components(
121
121
  "class ApiKeyMiddleware(BaseHTTPMiddleware):",
122
122
  " async def dispatch(self, request: Request, call_next):",
123
123
  " # Debug mode from environment",
124
- " debug = os.environ.get('GOLF_API_KEY_DEBUG', '').lower() == 'true'",
124
+ " debug = os.environ.get('API_KEY_DEBUG', '').lower() == 'true'",
125
125
  " ",
126
126
  " # Skip auth for monitoring endpoints",
127
127
  " path = request.url.path",
@@ -59,17 +59,6 @@ def generate_metrics_route(metrics_path: str) -> list[str]:
59
59
  ]
60
60
 
61
61
 
62
- def get_metrics_dependencies() -> list[str]:
63
- """Get list of metrics dependencies to add to pyproject.toml.
64
-
65
- Returns:
66
- List of package requirements strings
67
- """
68
- return [
69
- "prometheus-client>=0.19.0",
70
- ]
71
-
72
-
73
62
  def generate_metrics_instrumentation() -> list[str]:
74
63
  """Generate metrics instrumentation wrapper functions.
75
64
 
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(
@@ -172,24 +172,11 @@ def load_settings(project_path: str | Path) -> Settings:
172
172
  if env_file.exists():
173
173
  settings = Settings(_env_file=env_file)
174
174
 
175
- # Auto-enable OpenTelemetry if GOLF_API_KEY is present (from .env file)
176
- import os
177
-
178
- if os.environ.get("GOLF_API_KEY"):
179
- settings.opentelemetry_enabled = True
180
-
181
175
  # Try to load JSON config file first
182
176
  json_config_path = project_path / "golf.json"
183
177
  if json_config_path.exists():
184
178
  return _load_json_settings(json_config_path, settings)
185
179
 
186
- # No config file found, use defaults
187
- # Auto-enable OpenTelemetry if GOLF_API_KEY is present
188
- import os
189
-
190
- if os.environ.get("GOLF_API_KEY"):
191
- settings.opentelemetry_enabled = True
192
-
193
180
  return settings
194
181
 
195
182
 
@@ -206,12 +193,6 @@ def _load_json_settings(path: Path, settings: Settings) -> Settings:
206
193
  if hasattr(settings, key):
207
194
  setattr(settings, key, value)
208
195
 
209
- # Auto-enable OpenTelemetry if GOLF_API_KEY is present and telemetry wasn't explicitly configured
210
- import os
211
-
212
- if os.environ.get("GOLF_API_KEY") and "opentelemetry_enabled" not in config_data:
213
- settings.opentelemetry_enabled = True
214
-
215
196
  return settings
216
197
  except Exception as e:
217
198
  console.print(f"[bold red]Error loading JSON config from {path}: {e}[/bold red]")
@@ -1,8 +1,4 @@
1
1
  # Golf MCP Server Environment Variables
2
2
 
3
- # Golf platform integration
4
- GOLF_API_KEY="your-golf-api-key"
5
- GOLF_SERVER_ID="your-server-id"
6
-
7
3
  # Development tokens are configured in auth.py for this example
8
4
  # Additional environment variables can be added as needed
@@ -14,13 +14,6 @@ from collections import OrderedDict
14
14
 
15
15
  from opentelemetry import baggage, trace
16
16
 
17
- # Import endpoints with fallback for dev mode
18
- try:
19
- # In built wheels, this exists (generated from _endpoints.py.in)
20
- from golf import _endpoints # type: ignore
21
- except ImportError:
22
- # In editable/dev installs, fall back to env-based values
23
- from golf import _endpoints_fallback as _endpoints # type: ignore
24
17
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
25
18
  from opentelemetry.sdk.resources import Resource
26
19
  from opentelemetry.sdk.trace import TracerProvider
@@ -69,28 +62,6 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
69
62
  """
70
63
  global _provider
71
64
 
72
- # Check for Golf platform integration first
73
- golf_api_key = os.environ.get("GOLF_API_KEY")
74
- if golf_api_key:
75
- # Auto-configure for Golf platform - always use OTLP when Golf API
76
- # key is present
77
- os.environ["OTEL_TRACES_EXPORTER"] = "otlp_http"
78
-
79
- # Only set endpoint if not already configured (allow user override)
80
- if not os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"):
81
- os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = _endpoints.OTEL_ENDPOINT
82
-
83
- # Set Golf platform headers (append to existing if present)
84
- existing_headers = os.environ.get("OTEL_EXPORTER_OTLP_HEADERS", "")
85
- golf_header = f"X-Golf-Key={golf_api_key}"
86
-
87
- if existing_headers:
88
- # Check if Golf key is already in headers
89
- if "X-Golf-Key=" not in existing_headers:
90
- os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"{existing_headers},{golf_header}"
91
- else:
92
- os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = golf_header
93
-
94
65
  # Check for required environment variables based on exporter type
95
66
  exporter_type = os.environ.get("OTEL_TRACES_EXPORTER", "console").lower()
96
67
 
@@ -111,13 +82,6 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
111
82
  "service.instance.id": os.environ.get("SERVICE_INSTANCE_ID", "default"),
112
83
  }
113
84
 
114
- # Add Golf-specific attributes if available
115
- if golf_api_key:
116
- golf_server_id = os.environ.get("GOLF_SERVER_ID")
117
- if golf_server_id:
118
- resource_attributes["golf.server.id"] = golf_server_id
119
- resource_attributes["golf.platform.enabled"] = "true"
120
-
121
85
  resource = Resource.create(resource_attributes)
122
86
 
123
87
  # Create provider
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.2.10
3
+ Version: 0.2.12
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: typer>=0.15.4
23
23
  Requires-Dist: rich>=14.0.0
24
- Requires-Dist: fastmcp<3.0.0,>=2.11.0
24
+ Requires-Dist: fastmcp==2.12.5
25
25
  Requires-Dist: pydantic>=2.11.0
26
26
  Requires-Dist: pydantic-settings>=2.0.0
27
27
  Requires-Dist: python-dotenv>=1.1.0
@@ -197,9 +197,10 @@ from golf.utils import elicit, sample, get_context
197
197
  ```
198
198
 
199
199
  ```bash
200
- # Automatic telemetry with Golf Platform
201
- export GOLF_API_KEY="your-key"
202
- golf run # ✅ Telemetry enabled automatically
200
+ # Enable OpenTelemetry tracing
201
+ export OTEL_TRACES_EXPORTER="otlp_http"
202
+ export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318/v1/traces"
203
+ golf run # ✅ Telemetry enabled
203
204
  ```
204
205
 
205
206
  **[📚 Complete Documentation →](https://docs.golf.dev)**
@@ -220,7 +221,7 @@ Basic configuration in `golf.json`:
220
221
  ```
221
222
 
222
223
  - **`transport`**: Choose `"sse"`, `"streamable-http"`, or `"stdio"`
223
- - **`opentelemetry_enabled`**: Auto-enabled with `GOLF_API_KEY`
224
+ - **`opentelemetry_enabled`**: Enable OpenTelemetry tracing
224
225
  - **`detailed_tracing`**: Capture input/output (use carefully with sensitive data)
225
226
 
226
227
 
@@ -1,32 +1,28 @@
1
- golf/__init__.py,sha256=KrLUEDZhh_8Gu_JcwZgGUhFN9JR6Sjqt8v46gVPBsmE,307
2
- golf/_endpoints.py,sha256=-mvAmjx3YtqfAdajO13Kv7LKVBMdqsKqX0_3aCmCK4I,317
3
- golf/_endpoints.py.in,sha256=MeqcSF6xinnPknpBGF26xA9FYmSYKSWqCFXOSDlXiOA,216
4
- golf/_endpoints_fallback.py,sha256=CD6m45Ams1A9HVKowY8dCFUDMiFmJ8ZWSwHCENvU5u4,386
5
- golf/auth/__init__.py,sha256=gx62l8Z72eIldO2LtzKQ2OQVeobrhaSggTzJaZ0hcCo,8279
1
+ golf/__init__.py,sha256=X4KG3FscE5AhbGbcdDDgdDC550CVpxNMwdNLcx6EQ7M,23
2
+ golf/auth/__init__.py,sha256=sYWjWXGR70Ae87znseeBK2YWl8S1-qn_hndKf2xMULk,8173
6
3
  golf/auth/api_key.py,sha256=OonqTlG6USJxqK8TlPviJTv7sgYmTVfCxG_JsEjnEM4,2320
7
4
  golf/auth/factory.py,sha256=3-il1GbjjZlQfvWUZs-09r61Y_-b5cYEegWF7sY_cUs,13128
8
- golf/auth/helpers.py,sha256=WoYyjUAQwgDnLvJDvyhuEOmm8w0fQ-ZDqaYxs_lC8nw,8127
5
+ golf/auth/helpers.py,sha256=N5NUGqlBg8rwfYX6zXmsWnjDE2Bh9n1CHeKAqMShToc,5589
9
6
  golf/auth/providers.py,sha256=f89WeQUrYopS0GBcTO3yXlqyPQvG7s7GpaiUK2tb2ME,25048
10
7
  golf/auth/registry.py,sha256=Rjj7LnWvzEsI1GCnFbFcxpRllrVanD9bumWPaJ1giFQ,7960
11
8
  golf/cli/__init__.py,sha256=R8Y8KdD2C8gDo24fXGq-fdWWNeaq3MYjrbaSB8Hb-Hg,45
12
- golf/cli/branding.py,sha256=ndpy2kVxBzqr4fwsAlh_fbhxqgRPoF6kM3ufP9hg5QI,6896
13
- golf/cli/main.py,sha256=0TAccbjB9wmB55GmCV3X1CpzaOoMSzpQ-bx2oxi-THw,13536
9
+ golf/cli/branding.py,sha256=OdmCaYVg6g3P8fqSNDlTG8R5CwUTuqSd68BrpPTjKp8,6861
10
+ golf/cli/main.py,sha256=YnppG_I15wfVU-dELgJbGTjESMvXGmBpUds1JkE83Es,13403
14
11
  golf/commands/__init__.py,sha256=j2r-0Zg2BSqG7WtZczAtyAsrur7kRQpEN9ZJuoX1XQ8,119
15
12
  golf/commands/build.py,sha256=sLq9lSW4naq2vIlBreKI5SGnviQrhBWw13zfOZOKhuM,2293
16
13
  golf/commands/init.py,sha256=KkAg_3-KxBDFOcZqUHqcPAkDipykFVaLWpQ2tydnVPk,9617
17
- golf/commands/run.py,sha256=a8GSwLt6E2cUJespv-u3jbD-rNUMHqF3VArD1uXV-Vk,4299
14
+ golf/commands/run.py,sha256=2fdjqTfzQL-dFOqptkl-K2jel8VWpevAx0somcjNsPI,4576
18
15
  golf/core/__init__.py,sha256=4bKeskJ2fPaZqkz2xQScSa3phRLLrmrczwSL632jv-o,52
19
- golf/core/builder.py,sha256=-iUhj1G5SKSE9kG9Y2glxQPp3b45fqXVrYi3gQGmgbs,69963
20
- golf/core/builder_auth.py,sha256=SXPCpc5ipntoNqIAIA2ZCeGmEua6QVs7yC3MDtGKAro,8224
21
- golf/core/builder_metrics.py,sha256=j6Gtgd867o46JbDfSNGNsHt1QtV1XHKUJs1z8r4siQM,8830
16
+ golf/core/builder.py,sha256=ynmzTF1IsF5OTZl0dr-1Mzd8iEVNB-Oxa6RBWBu3DRs,76372
17
+ golf/core/builder_auth.py,sha256=f0w9TmxvNjxglcWY8NGULZJ8mwLqtUFFb2QHzgC0xAk,8219
18
+ golf/core/builder_metrics.py,sha256=wrXdE4_XWx7fUXnp61WU7rCBO-aG-4YzCMV6yO0z9Dg,8594
22
19
  golf/core/builder_telemetry.py,sha256=86bp7UlMUN6JyQRrZ5EizovP6AJ_q65OANJTeJXDIKc,3421
23
- golf/core/config.py,sha256=h8LWKdD-5vS8vRGs1qsCI2d8EbtjIKuXM3rUJ0T7EZ8,7452
20
+ golf/core/config.py,sha256=kL440b8i1dF8jdKeYwZZ04HdotX5CAlTyxR1ZyML-Iw,6850
24
21
  golf/core/parser.py,sha256=f9WqmLWlFuXQCObl2Qmna9bp_Bo0p0eIlukzwFaBSzo,43373
25
- golf/core/platform.py,sha256=0TxLfVPBhBR82aJAQWEJcnkGuQv65C9gYYy7P2foUVk,6662
26
22
  golf/core/telemetry.py,sha256=dXoWrgrQpj_HGrl_8TBZmRnuLxFKEn0GSDWQ9qq3ZQM,15686
27
23
  golf/core/transformer.py,sha256=iTDNFqdVeOjzl-acdjp8ZaTXss8Eptqu5cs4qLWt9SU,6914
28
24
  golf/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- golf/examples/basic/.env.example,sha256=hxl1B9Vo7nCN5dWvTeN6BpOxTwHwPLEQzsnToHhx7nk,256
25
+ golf/examples/basic/.env.example,sha256=Ktdc2Qta05smPItYHnEHhVxYPkq5JJ7i2aI5pOyyQx4,162
30
26
  golf/examples/basic/README.md,sha256=p8KMyaNXGZtrbgckW-Thu_5RKXViGiyZ5hHEiJ6Zc-U,3283
31
27
  golf/examples/basic/auth.py,sha256=cdPWFU__i8Jw8xI1hgGSobhaC4YIBKsh5LsQY2GS2dw,2789
32
28
  golf/examples/basic/golf.json,sha256=327fO4XmvksmZdlYtYPmRcB6TgEVkD1nSvHAuGyWsYA,98
@@ -43,14 +39,14 @@ golf/metrics/__init__.py,sha256=O91y-hj_E9R06gqV8pDZrzHxOIl-1T415Hj9RvFAp3o,283
43
39
  golf/metrics/collector.py,sha256=ScKeOyDNawZRMIRYBFqNqmaYAIVJQ2riMu_qOE9ugvU,10967
44
40
  golf/metrics/registry.py,sha256=mXQE4Pwf3PopGYjcUu4eGgPDAe085YWcsvcvWk0ny8Q,310
45
41
  golf/telemetry/__init__.py,sha256=txSXDBhug5uVMD1KjZszcILMrVTjvTCWlxo7TWwbul8,506
46
- golf/telemetry/instrumentation.py,sha256=kLWkvw14eCUVawNtGuRRzEqaVcwzLJxk86uvDqW_tOU,57771
42
+ golf/telemetry/instrumentation.py,sha256=qBgSw8KqK_nroOjaeI1HMxaBUPsrIERyzp0ig842nu8,56159
47
43
  golf/utilities/__init__.py,sha256=X9iY9yi3agz1GVcn8-qWeOCt8CSSsruHxqPNtiF63TY,530
48
44
  golf/utilities/context.py,sha256=DGGvhVe---QMhy0wtdWhNp-_WVk1NvAcOFn0uBKBpYo,1579
49
45
  golf/utilities/elicitation.py,sha256=MParZZZsY45s70-KXduHa6IvpWXnLW2FCPfrGijMaHs,5223
50
46
  golf/utilities/sampling.py,sha256=88nDv-trBE4gZQbcnMjXl3LW6TiIhv5zR_cuEIGjaIM,7233
51
- golf_mcp-0.2.10.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
52
- golf_mcp-0.2.10.dist-info/METADATA,sha256=4YRKbcRjyZfBFPFcnzJ5ZV8hU3enJEVmQy2VumwVlpc,9371
53
- golf_mcp-0.2.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
- golf_mcp-0.2.10.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
55
- golf_mcp-0.2.10.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
56
- golf_mcp-0.2.10.dist-info/RECORD,,
47
+ golf_mcp-0.2.12.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
48
+ golf_mcp-0.2.12.dist-info/METADATA,sha256=RksLyay82K3poGEAup26_dMaRw06Z9ZnOwC3iFWHel8,9414
49
+ golf_mcp-0.2.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
50
+ golf_mcp-0.2.12.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
51
+ golf_mcp-0.2.12.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
52
+ golf_mcp-0.2.12.dist-info/RECORD,,
golf/_endpoints.py DELETED
@@ -1,6 +0,0 @@
1
- # Auto-generated at build time by setup.py:build_py
2
- # This template contains placeholders that are replaced during build
3
-
4
- # Platform endpoints
5
- PLATFORM_API_URL = "https://golf-backend.golf-auth-1.authed-qukc4.ryvn.run/api/resources"
6
- OTEL_ENDPOINT = "https://golf-backend.golf-auth-1.authed-qukc4.ryvn.run/api/v1/otel"
golf/_endpoints.py.in DELETED
@@ -1,6 +0,0 @@
1
- # Auto-generated at build time by setup.py:build_py
2
- # This template contains placeholders that are replaced during build
3
-
4
- # Platform endpoints
5
- PLATFORM_API_URL = "{PLATFORM_API_URL}"
6
- OTEL_ENDPOINT = "{OTEL_ENDPOINT}"
@@ -1,10 +0,0 @@
1
- """Fallback endpoints for development/editable installs."""
2
-
3
- import os
4
-
5
- # These are used when the generated _endpoints.py doesn't exist (dev mode)
6
- # or when environment variables override the built-in values
7
-
8
- PLATFORM_API_URL = os.getenv("GOLF_PLATFORM_API_URL", "http://localhost:8000/api/resources")
9
-
10
- OTEL_ENDPOINT = os.getenv("GOLF_OTEL_ENDPOINT", "http://localhost:4318/v1/traces")
golf/core/platform.py DELETED
@@ -1,182 +0,0 @@
1
- """Platform registration for Golf MCP projects."""
2
-
3
- import os
4
- from datetime import datetime
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- import httpx
9
- from rich.console import Console
10
-
11
- from golf import __version__
12
- from golf.core.config import Settings
13
- from golf.core.parser import ComponentType, ParsedComponent
14
-
15
- # Import endpoints with fallback for dev mode
16
- try:
17
- # In built wheels, this exists (generated from _endpoints.py.in)
18
- from golf import _endpoints # type: ignore
19
- except ImportError:
20
- # In editable/dev installs, fall back to env-based values
21
- from golf import _endpoints_fallback as _endpoints # type: ignore
22
-
23
- console = Console()
24
-
25
-
26
- async def register_project_with_platform(
27
- project_path: Path,
28
- settings: Settings,
29
- components: dict[ComponentType, list[ParsedComponent]],
30
- ) -> bool:
31
- """Register project with Golf platform during prod build.
32
-
33
- Args:
34
- project_path: Path to the project root
35
- settings: Project settings
36
- components: Parsed components dictionary
37
-
38
- Returns:
39
- True if registration succeeded or was skipped, False if failed
40
- """
41
- # Check if platform integration is enabled
42
- api_key = os.getenv("GOLF_API_KEY")
43
- if not api_key:
44
- return True # Skip silently if no API key
45
-
46
- # Require explicit server ID
47
- server_id = os.getenv("GOLF_SERVER_ID")
48
- if not server_id:
49
- console.print(
50
- "[yellow]Warning: Platform registration skipped - GOLF_SERVER_ID environment variable required[/yellow]"
51
- )
52
- return True # Skip registration but don't fail build
53
-
54
- # Build metadata payload
55
- metadata = {
56
- "project_name": settings.name,
57
- "description": settings.description,
58
- "server_id": server_id,
59
- "components": _build_component_list(components, project_path),
60
- "build_timestamp": datetime.utcnow().isoformat(),
61
- "golf_version": __version__,
62
- "component_counts": _get_component_counts(components),
63
- "server_config": {
64
- "host": settings.host,
65
- "port": settings.port,
66
- "transport": settings.transport,
67
- "auth_enabled": bool(settings.auth),
68
- "telemetry_enabled": settings.opentelemetry_enabled,
69
- "health_check_enabled": settings.health_check_enabled,
70
- },
71
- }
72
-
73
- try:
74
- async with httpx.AsyncClient(timeout=10.0) as client:
75
- response = await client.post(
76
- _endpoints.PLATFORM_API_URL,
77
- json=metadata,
78
- headers={
79
- "X-Golf-Key": api_key,
80
- "Content-Type": "application/json",
81
- "User-Agent": f"Golf-MCP/{__version__}",
82
- },
83
- )
84
- response.raise_for_status()
85
-
86
- console.print("[green]✓[/green] Registered with Golf platform")
87
- return True
88
-
89
- except httpx.TimeoutException:
90
- console.print("[yellow]Warning: Platform registration timed out[/yellow]")
91
- return False
92
- except httpx.HTTPStatusError as e:
93
- if e.response.status_code == 401:
94
- console.print("[yellow]Warning: Platform registration failed - invalid API key[/yellow]")
95
- elif e.response.status_code == 403:
96
- console.print("[yellow]Warning: Platform registration failed - access denied[/yellow]")
97
- else:
98
- console.print(f"[yellow]Warning: Platform registration failed - HTTP {e.response.status_code}[/yellow]")
99
- return False
100
- except Exception as e:
101
- console.print(f"[yellow]Warning: Platform registration failed: {e}[/yellow]")
102
- return False # Don't fail the build
103
-
104
-
105
- def _build_component_list(
106
- components: dict[ComponentType, list[ParsedComponent]],
107
- project_path: Path,
108
- ) -> list[dict[str, Any]]:
109
- """Convert parsed components to platform format.
110
-
111
- Args:
112
- components: Dictionary of parsed components by type
113
- project_path: Path to the project root
114
-
115
- Returns:
116
- List of component metadata dictionaries
117
- """
118
- result = []
119
-
120
- for comp_type, comp_list in components.items():
121
- for comp in comp_list:
122
- # Start with basic component data
123
- component_data = {
124
- "name": comp.name,
125
- "type": comp_type.value,
126
- "description": comp.docstring,
127
- "entry_function": comp.entry_function,
128
- "parent_module": comp.parent_module,
129
- }
130
-
131
- # Add file path relative to project root if available
132
- if comp.file_path:
133
- try:
134
- file_path = Path(comp.file_path)
135
- # Use the provided project_path for relative calculation
136
- rel_path = file_path.relative_to(project_path)
137
- component_data["file_path"] = str(rel_path)
138
- except ValueError:
139
- # If relative_to fails, try to find a common path or use filename
140
- component_data["file_path"] = Path(comp.file_path).name
141
-
142
- # Add schema information only if available and not None
143
- if hasattr(comp, "input_schema") and comp.input_schema:
144
- component_data["input_schema"] = comp.input_schema
145
- if hasattr(comp, "output_schema") and comp.output_schema:
146
- component_data["output_schema"] = comp.output_schema
147
-
148
- # Add component-specific fields only if they have values
149
- if comp_type == ComponentType.RESOURCE:
150
- if hasattr(comp, "uri_template") and comp.uri_template:
151
- component_data["uri_template"] = comp.uri_template
152
-
153
- elif comp_type == ComponentType.TOOL:
154
- if hasattr(comp, "annotations") and comp.annotations:
155
- component_data["annotations"] = comp.annotations
156
-
157
- # Add parameters only if they exist and are not empty
158
- if hasattr(comp, "parameters") and comp.parameters:
159
- component_data["parameters"] = comp.parameters
160
-
161
- result.append(component_data)
162
-
163
- return result
164
-
165
-
166
- def _get_component_counts(
167
- components: dict[ComponentType, list[ParsedComponent]],
168
- ) -> dict[str, int]:
169
- """Get component counts by type.
170
-
171
- Args:
172
- components: Dictionary of parsed components by type
173
-
174
- Returns:
175
- Dictionary with counts for each component type
176
- """
177
- return {
178
- "tools": len(components.get(ComponentType.TOOL, [])),
179
- "resources": len(components.get(ComponentType.RESOURCE, [])),
180
- "prompts": len(components.get(ComponentType.PROMPT, [])),
181
- "total": sum(len(comp_list) for comp_list in components.values()),
182
- }