golf-mcp 0.1.20__py3-none-any.whl → 0.2.1__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.

Files changed (123) hide show
  1. golf/__init__.py +9 -1
  2. golf/_endpoints.py +6 -0
  3. golf/_endpoints_fallback.py +10 -0
  4. golf/auth/__init__.py +235 -83
  5. golf/auth/api_key.py +6 -14
  6. golf/auth/factory.py +358 -0
  7. golf/auth/helpers.py +12 -42
  8. golf/auth/providers.py +446 -0
  9. golf/auth/registry.py +256 -0
  10. golf/cli/branding.py +192 -0
  11. golf/cli/main.py +28 -69
  12. golf/commands/__init__.py +2 -0
  13. golf/commands/build.py +4 -7
  14. golf/commands/init.py +30 -53
  15. golf/commands/run.py +50 -20
  16. golf/core/builder.py +355 -414
  17. golf/core/builder_auth.py +63 -144
  18. golf/core/builder_telemetry.py +26 -3
  19. golf/core/config.py +38 -59
  20. golf/core/parser.py +132 -139
  21. golf/core/platform.py +12 -10
  22. golf/core/telemetry.py +11 -19
  23. golf/core/transformer.py +38 -15
  24. golf/examples/__pycache__/__init__.cpython-311.pyc +0 -0
  25. golf/examples/basic/.coverage +0 -0
  26. golf/examples/basic/.env.example +8 -4
  27. golf/examples/basic/README.md +117 -45
  28. golf/examples/basic/__pycache__/auth.cpython-311.pyc +0 -0
  29. golf/examples/basic/auth.py +76 -0
  30. golf/examples/basic/golf.json +2 -5
  31. golf/examples/basic/htmlcov/.gitignore +2 -0
  32. golf/examples/basic/htmlcov/class_index.html +547 -0
  33. golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +733 -0
  34. golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
  35. golf/examples/basic/htmlcov/function_index.html +2091 -0
  36. golf/examples/basic/htmlcov/index.html +349 -0
  37. golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
  38. golf/examples/basic/htmlcov/status.json +1 -0
  39. golf/examples/basic/htmlcov/style_cb_8e611ae1.css +337 -0
  40. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +323 -0
  41. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +170 -0
  42. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +430 -0
  43. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +288 -0
  44. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +493 -0
  45. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +353 -0
  46. golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +120 -0
  47. golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +1535 -0
  48. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +98 -0
  49. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +289 -0
  50. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +476 -0
  51. golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +97 -0
  52. golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +102 -0
  53. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +178 -0
  54. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +387 -0
  55. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +222 -0
  56. golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +106 -0
  57. golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +107 -0
  58. golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +98 -0
  59. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +306 -0
  60. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +329 -0
  61. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +1471 -0
  62. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +186 -0
  63. golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +315 -0
  64. golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +1149 -0
  65. golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +279 -0
  66. golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +589 -0
  67. golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +286 -0
  68. golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +107 -0
  69. golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +417 -0
  70. golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +109 -0
  71. golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +109 -0
  72. golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +150 -0
  73. golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +267 -0
  74. golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +318 -0
  75. golf/examples/basic/prompts/__pycache__/welcome.cpython-311.pyc +0 -0
  76. golf/examples/basic/prompts/welcome.py +3 -5
  77. golf/examples/basic/resources/__pycache__/current_time.cpython-311.pyc +0 -0
  78. golf/examples/basic/resources/__pycache__/info.cpython-311.pyc +0 -0
  79. golf/examples/basic/resources/current_time.py +5 -13
  80. golf/examples/basic/resources/weather/__pycache__/common.cpython-311.pyc +0 -0
  81. golf/examples/basic/resources/weather/__pycache__/current.cpython-311.pyc +0 -0
  82. golf/examples/basic/resources/weather/__pycache__/forecast.cpython-311.pyc +0 -0
  83. golf/examples/basic/resources/weather/city.py +46 -0
  84. golf/examples/basic/resources/weather/common.py +4 -11
  85. golf/examples/basic/resources/weather/current.py +5 -5
  86. golf/examples/basic/resources/weather/forecast.py +5 -5
  87. golf/examples/basic/tools/__pycache__/calculator.cpython-311.pyc +0 -0
  88. golf/examples/basic/tools/calculator.py +94 -0
  89. golf/examples/basic/tools/say/__pycache__/hello.cpython-311.pyc +0 -0
  90. golf/examples/basic/tools/say/hello.py +65 -0
  91. golf/metrics/collector.py +100 -19
  92. golf/telemetry/__init__.py +4 -0
  93. golf/telemetry/instrumentation.py +484 -178
  94. golf/utilities/__init__.py +12 -0
  95. golf/utilities/context.py +53 -0
  96. golf/utilities/elicitation.py +170 -0
  97. golf/utilities/sampling.py +221 -0
  98. {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.1.dist-info}/METADATA +51 -104
  99. golf_mcp-0.2.1.dist-info/RECORD +110 -0
  100. golf/auth/oauth.py +0 -861
  101. golf/auth/provider.py +0 -115
  102. golf/examples/api_key/.env +0 -2
  103. golf/examples/api_key/.env.example +0 -1
  104. golf/examples/api_key/README.md +0 -84
  105. golf/examples/api_key/golf.json +0 -8
  106. golf/examples/api_key/pre_build.py +0 -11
  107. golf/examples/api_key/tools/issues/create.py +0 -93
  108. golf/examples/api_key/tools/issues/list.py +0 -92
  109. golf/examples/api_key/tools/repos/list.py +0 -111
  110. golf/examples/api_key/tools/search/code.py +0 -106
  111. golf/examples/api_key/tools/users/get.py +0 -82
  112. golf/examples/basic/.env +0 -5
  113. golf/examples/basic/pre_build.py +0 -28
  114. golf/examples/basic/tools/github_user.py +0 -65
  115. golf/examples/basic/tools/hello.py +0 -34
  116. golf/examples/basic/tools/payments/charge.py +0 -70
  117. golf/examples/basic/tools/payments/common.py +0 -36
  118. golf/examples/basic/tools/payments/refund.py +0 -61
  119. golf_mcp-0.1.20.dist-info/RECORD +0 -60
  120. {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.1.dist-info}/WHEEL +0 -0
  121. {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.1.dist-info}/entry_points.txt +0 -0
  122. {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.1.dist-info}/licenses/LICENSE +0 -0
  123. {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.1.dist-info}/top_level.txt +0 -0
golf/core/builder_auth.py CHANGED
@@ -1,22 +1,25 @@
1
- """Authentication integration for the GolfMCP build process.
1
+ """Authentication integration for the Golf MCP build process.
2
2
 
3
3
  This module adds support for injecting authentication configuration
4
- into the generated FastMCP application during the build process.
4
+ into the generated FastMCP application during the build process using
5
+ FastMCP 2.11+ built-in auth providers.
5
6
  """
6
7
 
7
- from golf.auth import get_auth_config
8
+ from golf.auth import get_auth_config, is_auth_configured
8
9
  from golf.auth.api_key import get_api_key_config
10
+ from golf.auth.providers import AuthConfig
9
11
 
10
12
 
11
13
  def generate_auth_code(
12
14
  server_name: str,
13
- host: str = "127.0.0.1",
15
+ host: str = "localhost",
14
16
  port: int = 3000,
15
17
  https: bool = False,
16
18
  opentelemetry_enabled: bool = False,
17
19
  transport: str = "streamable-http",
18
20
  ) -> dict:
19
- """Generate authentication components for the FastMCP app.
21
+ """Generate authentication components for the FastMCP app using modern
22
+ auth providers.
20
23
 
21
24
  Returns a dictionary with:
22
25
  - imports: List of import statements
@@ -27,116 +30,49 @@ def generate_auth_code(
27
30
  # Check for API key configuration first
28
31
  api_key_config = get_api_key_config()
29
32
  if api_key_config:
30
- return generate_api_key_auth_components(
31
- server_name, opentelemetry_enabled, transport
32
- )
33
-
34
- # Otherwise check for OAuth configuration
35
- original_provider_config, required_scopes_from_config = get_auth_config()
33
+ return generate_api_key_auth_components(server_name, opentelemetry_enabled, transport)
36
34
 
37
- if not original_provider_config:
35
+ # Check for modern auth configuration
36
+ auth_config = get_auth_config()
37
+ if not auth_config:
38
38
  # If no auth config, return empty components
39
39
  return {"imports": [], "setup_code": [], "fastmcp_args": {}, "has_auth": False}
40
40
 
41
- # Build auth components
41
+ # Validate that we have a modern auth config
42
+ if not isinstance(auth_config, AuthConfig):
43
+ raise ValueError(
44
+ f"Invalid auth configuration type: {type(auth_config).__name__}. "
45
+ "Golf 0.2.x requires modern auth configurations (JWTAuthConfig, "
46
+ "StaticTokenConfig, OAuthServerConfig, or RemoteAuthConfig). "
47
+ "Please update your auth.py file."
48
+ )
49
+
50
+ # Generate modern auth components with embedded configuration
42
51
  auth_imports = [
43
52
  "import os",
44
- "import sys # For stderr output",
45
- "from mcp.server.auth.settings import AuthSettings, ClientRegistrationOptions",
46
- "from golf.auth.provider import ProviderConfig as GolfProviderConfigInternal # Alias to avoid conflict if user also has ProviderConfig",
47
- "from golf.auth.oauth import GolfOAuthProvider",
48
- "# get_access_token and create_callback_handler are used by generated auth_routes",
49
- "from golf.auth import get_access_token, create_callback_handler",
53
+ "import sys",
54
+ "from golf.auth.factory import create_auth_provider",
55
+ "from golf.auth.providers import RemoteAuthConfig, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig, OAuthProxyConfig",
50
56
  ]
51
57
 
52
- setup_code_lines = []
53
-
54
- # Code to determine runtime server address configuration
55
- setup_code_lines.extend(
56
- [
57
- "# Determine runtime server address configuration",
58
- f"runtime_host = os.environ.get('HOST', {repr(host)})",
59
- f"runtime_port = int(os.environ.get('PORT', {repr(port)}))",
60
- f"runtime_protocol = 'https' if os.environ.get('HTTPS', {repr(str(https)).lower()}).lower() in ('1', 'true', 'yes') else 'http'",
61
- "",
62
- "# Determine proper issuer URL at runtime for this server instance",
63
- "include_port = (runtime_protocol == 'http' and runtime_port != 80) or (runtime_protocol == 'https' and runtime_port != 443)",
64
- "if include_port:",
65
- " runtime_issuer_url = f'{runtime_protocol}://{runtime_host}:{runtime_port}'",
66
- "else:",
67
- " runtime_issuer_url = f'{runtime_protocol}://{runtime_host}'",
68
- "",
69
- ]
70
- )
58
+ # Embed the auth configuration directly in the generated code
59
+ # Convert the auth config to its string representation for embedding
60
+ auth_config_repr = repr(auth_config)
71
61
 
72
- # Code to load secrets from environment variables AT RUNTIME in server.py
73
- setup_code_lines.extend(
74
- [
75
- "# Load secrets from environment variables using names specified in pre_build.py ProviderConfig",
76
- f"runtime_client_id = os.environ.get({repr(original_provider_config.client_id_env_var)})",
77
- f"runtime_client_secret = os.environ.get({repr(original_provider_config.client_secret_env_var)})",
78
- f"runtime_jwt_secret = os.environ.get({repr(original_provider_config.jwt_secret_env_var)})",
79
- "",
80
- "# Check and warn if essential secrets are missing",
81
- "if not runtime_client_id:",
82
- f" print(f\"AUTH WARNING: Environment variable '{original_provider_config.client_id_env_var}' for OAuth Client ID is not set. Authentication will likely fail.\", file=sys.stderr)",
83
- "if not runtime_client_secret:",
84
- f" print(f\"AUTH WARNING: Environment variable '{original_provider_config.client_secret_env_var}' for OAuth Client Secret is not set. Authentication will likely fail.\", file=sys.stderr)",
85
- "if not runtime_jwt_secret:",
86
- f" print(f\"AUTH WARNING: Environment variable '{original_provider_config.jwt_secret_env_var}' for JWT Secret is not set. Using a default insecure fallback. DO NOT USE IN PRODUCTION.\", file=sys.stderr)",
87
- f" runtime_jwt_secret = {repr(f'fallback-dev-jwt-secret-for-{server_name}-!PLEASE-CHANGE-THIS!')}", # Fixed fallback string
88
- "",
89
- ]
90
- )
91
-
92
- # Code to instantiate ProviderConfig using runtime-loaded secrets and other baked-in non-secrets
93
- setup_code_lines.extend(
94
- [
95
- "# Instantiate ProviderConfig with runtime-resolved secrets and other pre-configured values",
96
- "provider_config_instance = GolfProviderConfigInternal(", # Use aliased import
97
- f" provider={repr(original_provider_config.provider)},",
98
- f" client_id_env_var={repr(original_provider_config.client_id_env_var)},",
99
- f" client_secret_env_var={repr(original_provider_config.client_secret_env_var)},",
100
- f" jwt_secret_env_var={repr(original_provider_config.jwt_secret_env_var)},",
101
- " client_id=runtime_client_id,",
102
- " client_secret=runtime_client_secret,",
103
- " jwt_secret=runtime_jwt_secret,",
104
- f" authorize_url={repr(original_provider_config.authorize_url)},",
105
- f" token_url={repr(original_provider_config.token_url)},",
106
- f" userinfo_url={repr(original_provider_config.userinfo_url)},",
107
- f" jwks_uri={repr(original_provider_config.jwks_uri)},",
108
- f" scopes={repr(original_provider_config.scopes)},",
109
- " issuer_url=runtime_issuer_url,",
110
- f" callback_path={repr(original_provider_config.callback_path)},",
111
- f" token_expiration={original_provider_config.token_expiration}",
112
- ")",
113
- "",
114
- "auth_provider = GolfOAuthProvider(provider_config_instance)",
115
- "from golf.auth.helpers import _set_active_golf_oauth_provider # Ensure helper is imported",
116
- "_set_active_golf_oauth_provider(auth_provider) # Make provider instance available",
117
- "",
118
- ]
119
- )
120
-
121
- # AuthSettings creation
122
- setup_code_lines.extend(
123
- [
124
- "# Create auth settings for FastMCP",
125
- "auth_settings = AuthSettings(",
126
- " issuer_url=runtime_issuer_url,",
127
- " client_registration_options=ClientRegistrationOptions(",
128
- " enabled=True,",
129
- f" valid_scopes={repr(original_provider_config.scopes)},",
130
- f" default_scopes={repr(original_provider_config.scopes)}",
131
- " ),",
132
- f" required_scopes={repr(required_scopes_from_config) if required_scopes_from_config else None}",
133
- ")",
134
- "",
135
- ]
136
- )
62
+ setup_code_lines = [
63
+ "# Modern FastMCP 2.11+ authentication setup with embedded configuration",
64
+ f"auth_config = {auth_config_repr}",
65
+ "try:",
66
+ " auth_provider = create_auth_provider(auth_config)",
67
+ " print(f'Authentication configured with {auth_config.provider_type} provider')",
68
+ "except Exception as e:",
69
+ " print(f'Authentication setup failed: {e}', file=sys.stderr)",
70
+ " auth_provider = None",
71
+ "",
72
+ ]
137
73
 
138
- # FastMCP constructor arguments
139
- fastmcp_args = {"auth_server_provider": "auth_provider", "auth": "auth_settings"}
74
+ # FastMCP constructor arguments - FastMCP 2.11+ uses auth parameter
75
+ fastmcp_args = {"auth": "auth_provider"}
140
76
 
141
77
  return {
142
78
  "imports": auth_imports,
@@ -174,7 +110,7 @@ def generate_api_key_auth_components(
174
110
  ]
175
111
 
176
112
  setup_code_lines = [
177
- "# Recreate API key configuration from pre_build.py",
113
+ "# Recreate API key configuration from auth.py",
178
114
  "configure_api_key(",
179
115
  f" header_name={repr(api_key_config.header_name)},",
180
116
  f" header_prefix={repr(api_key_config.header_prefix)},",
@@ -221,7 +157,8 @@ def generate_api_key_auth_components(
221
157
  " # Check if API key is required but missing",
222
158
  " if api_key_config.required and not api_key:",
223
159
  " return JSONResponse(",
224
- " {'error': 'unauthorized', 'detail': f'Missing required {header_name} header'},",
160
+ " {'error': 'unauthorized', "
161
+ "'detail': f'Missing required {header_name} header'},"
225
162
  " status_code=401,",
226
163
  " headers={'WWW-Authenticate': f'{header_name} realm=\"MCP Server\"'}",
227
164
  " )",
@@ -243,48 +180,30 @@ def generate_api_key_auth_components(
243
180
 
244
181
 
245
182
  def generate_auth_routes() -> str:
246
- """Generate code for OAuth routes in the FastMCP app.
247
- These routes are added to the FastMCP instance (`mcp`) created by `generate_auth_code`.
183
+ """Generate code for auth routes in the FastMCP app.
184
+
185
+ Auth providers (RemoteAuthProvider, OAuthProvider) provide OAuth metadata routes
186
+ that need to be added to the server.
248
187
  """
249
188
  # API key auth doesn't need special routes
250
189
  api_key_config = get_api_key_config()
251
190
  if api_key_config:
252
191
  return ""
253
192
 
254
- provider_config, _ = get_auth_config() # Used to check if auth is enabled generally
255
- if not provider_config:
193
+ # Check if auth is configured
194
+ if not is_auth_configured():
256
195
  return ""
257
196
 
258
- auth_routes_list = [
259
- "",
260
- "# Auth-specific routes, using the 'auth_provider' instance defined in the auth setup code",
261
- "@mcp.custom_route('/auth/callback', methods=['GET'])",
262
- "async def oauth_callback(request):",
263
- " # create_callback_handler is imported in the auth setup code block",
264
- " handler = create_callback_handler(auth_provider)",
265
- " return await handler(request)",
266
- "",
267
- "@mcp.custom_route('/login', methods=['GET'])",
268
- "async def login(request):",
269
- " from starlette.responses import RedirectResponse",
270
- " import urllib.parse",
271
- ' default_redirect_uri = urllib.parse.quote_plus("http://localhost:5173/callback")',
272
- ' authorize_url = f"/mcp/auth/authorize?client_id=default&response_type=code&redirect_uri={default_redirect_uri}"',
273
- " return RedirectResponse(authorize_url)",
274
- "",
275
- "@mcp.custom_route('/auth-error', methods=['GET'])",
276
- "async def auth_error(request):",
277
- " from starlette.responses import HTMLResponse",
278
- " error = request.query_params.get('error', 'unknown_error')",
279
- " error_desc = request.query_params.get('error_description', 'An authentication error occurred')",
280
- ' html_content = f"""',
281
- " <!DOCTYPE html>",
282
- " <html><head><title>Authentication Error</title>",
283
- " <style> body {{ font-family: system-ui, sans-serif; padding: 2rem; max-width: 600px; margin: auto; }} h1 {{ color: #c53030; }} .error-box {{ background-color: #fed7d7; border: 1px solid #f56565; border-radius: 0.25rem; padding: 1rem; margin: 1rem 0; }} .btn {{ background-color: #4299e1; color: white; border: none; padding: 0.5rem 1rem; border-radius: 0.25rem; cursor: pointer; text-decoration: none; display: inline-block; margin-top: 1rem; }} .btn:hover {{ background-color: #2b6cb0; }} </style>",
284
- " </head><body><h1>Authentication Failed</h1><div class='error-box'>",
285
- " <p><strong>Error:</strong> {error}</p><p><strong>Description:</strong> {error_desc}</p></div>",
286
- " <a href='/login' class='btn'>Try Again</a></body></html>\"\"\"",
287
- " return HTMLResponse(content=html_content)",
288
- "",
289
- ]
290
- return "\n".join(auth_routes_list)
197
+ # Auth providers provide OAuth metadata routes that need to be added to the server
198
+ return """
199
+ # Add OAuth metadata routes from auth provider
200
+ if auth_provider and hasattr(auth_provider, 'get_routes'):
201
+ auth_routes = auth_provider.get_routes()
202
+ if auth_routes:
203
+ # Add routes to FastMCP's additional HTTP routes list
204
+ try:
205
+ mcp._additional_http_routes.extend(auth_routes)
206
+ print(f"Added {len(auth_routes)} OAuth metadata routes")
207
+ except Exception as e:
208
+ print(f"Warning: Failed to add OAuth routes: {e}")
209
+ """
@@ -29,6 +29,7 @@ def generate_component_registration_with_telemetry(
29
29
  entry_function: str,
30
30
  docstring: str = "",
31
31
  uri_template: str = None,
32
+ is_template: bool = False,
32
33
  ) -> str:
33
34
  """Generate component registration code with telemetry instrumentation.
34
35
 
@@ -39,6 +40,7 @@ def generate_component_registration_with_telemetry(
39
40
  entry_function: Entry function name
40
41
  docstring: Component description
41
42
  uri_template: URI template for resources (optional)
43
+ is_template: Whether the resource is a template (has URI parameters)
42
44
 
43
45
  Returns:
44
46
  Python code string for registering the component with instrumentation
@@ -48,15 +50,36 @@ def generate_component_registration_with_telemetry(
48
50
 
49
51
  if component_type == "tool":
50
52
  wrapped_func = f"instrument_tool({func_ref}, '{component_name}')"
51
- return f'mcp.add_tool({wrapped_func}, name="{component_name}", description="{escaped_docstring}")'
53
+ return (
54
+ f"_tool = Tool.from_function({wrapped_func}, "
55
+ f'name="{component_name}", description="{escaped_docstring}")\n'
56
+ f"mcp.add_tool(_tool)"
57
+ )
52
58
 
53
59
  elif component_type == "resource":
54
60
  wrapped_func = f"instrument_resource({func_ref}, '{uri_template}')"
55
- return f'mcp.add_resource_fn({wrapped_func}, uri="{uri_template}", name="{component_name}", description="{escaped_docstring}")'
61
+ if is_template:
62
+ return (
63
+ f"_resource = ResourceTemplate.from_function({wrapped_func}, "
64
+ f'uri_template="{uri_template}", name="{component_name}", '
65
+ f'description="{escaped_docstring}")\n'
66
+ f"mcp.add_template(_resource)"
67
+ )
68
+ else:
69
+ return (
70
+ f"_resource = Resource.from_function({wrapped_func}, "
71
+ f'uri="{uri_template}", name="{component_name}", '
72
+ f'description="{escaped_docstring}")\n'
73
+ f"mcp.add_resource(_resource)"
74
+ )
56
75
 
57
76
  elif component_type == "prompt":
58
77
  wrapped_func = f"instrument_prompt({func_ref}, '{component_name}')"
59
- return f'mcp.add_prompt({wrapped_func}, name="{component_name}", description="{escaped_docstring}")'
78
+ return (
79
+ f"_prompt = Prompt.from_function({wrapped_func}, "
80
+ f'name="{component_name}", description="{escaped_docstring}")\n'
81
+ f"mcp.add_prompt(_prompt)"
82
+ )
60
83
 
61
84
  else:
62
85
  raise ValueError(f"Unknown component type: {component_type}")
golf/core/config.py CHANGED
@@ -13,19 +13,11 @@ console = Console()
13
13
  class AuthConfig(BaseModel):
14
14
  """Authentication configuration."""
15
15
 
16
- provider: str = Field(
17
- ..., description="Authentication provider (e.g., 'jwks', 'google', 'github')"
18
- )
16
+ provider: str = Field(..., description="Authentication provider (e.g., 'jwks', 'google', 'github')")
19
17
  scopes: list[str] = Field(default_factory=list, description="Required OAuth scopes")
20
- client_id_env: str | None = Field(
21
- None, description="Environment variable name for client ID"
22
- )
23
- client_secret_env: str | None = Field(
24
- None, description="Environment variable name for client secret"
25
- )
26
- redirect_uri: str | None = Field(
27
- None, description="OAuth redirect URI (defaults to localhost callback)"
28
- )
18
+ client_id_env: str | None = Field(None, description="Environment variable name for client ID")
19
+ client_secret_env: str | None = Field(None, description="Environment variable name for client secret")
20
+ redirect_uri: str | None = Field(None, description="OAuth redirect URI (defaults to localhost callback)")
29
21
 
30
22
  @field_validator("provider")
31
23
  @classmethod
@@ -33,10 +25,7 @@ class AuthConfig(BaseModel):
33
25
  """Validate the provider value."""
34
26
  valid_providers = {"jwks", "google", "github", "custom"}
35
27
  if value not in valid_providers and not value.startswith("custom:"):
36
- raise ValueError(
37
- f"Invalid provider '{value}'. Must be one of {valid_providers} "
38
- "or start with 'custom:'"
39
- )
28
+ raise ValueError(f"Invalid provider '{value}'. Must be one of {valid_providers} or start with 'custom:'")
40
29
  return value
41
30
 
42
31
 
@@ -44,9 +33,7 @@ class DeployConfig(BaseModel):
44
33
  """Deployment configuration."""
45
34
 
46
35
  default: str = Field("vercel", description="Default deployment target")
47
- options: dict[str, Any] = Field(
48
- default_factory=dict, description="Target-specific options"
49
- )
36
+ options: dict[str, Any] = Field(default_factory=dict, description="Target-specific options")
50
37
 
51
38
 
52
39
  class Settings(BaseSettings):
@@ -64,10 +51,10 @@ class Settings(BaseSettings):
64
51
  description: str | None = Field(None, description="Project description")
65
52
 
66
53
  # Build settings
67
- output_dir: str = Field("build", description="Build artifact folder")
54
+ output_dir: str = Field("dist", description="Build artifact folder")
68
55
 
69
56
  # Server settings
70
- host: str = Field("127.0.0.1", description="Server host")
57
+ host: str = Field("localhost", description="Server host")
71
58
  port: int = Field(3000, description="Server port")
72
59
  transport: str = Field(
73
60
  "streamable-http",
@@ -75,37 +62,28 @@ class Settings(BaseSettings):
75
62
  )
76
63
 
77
64
  # Auth settings
78
- auth: str | AuthConfig | None = Field(
79
- None, description="Authentication configuration or URI"
80
- )
65
+ auth: str | AuthConfig | None = Field(None, description="Authentication configuration or URI")
81
66
 
82
67
  # Deploy settings
83
- deploy: DeployConfig = Field(
84
- default_factory=DeployConfig, description="Deployment configuration"
85
- )
68
+ deploy: DeployConfig = Field(default_factory=DeployConfig, description="Deployment configuration")
86
69
 
87
70
  # Feature flags
88
71
  telemetry: bool = Field(True, description="Enable anonymous telemetry")
89
72
 
90
73
  # Project paths
91
74
  tools_dir: str = Field("tools", description="Directory containing tools")
92
- resources_dir: str = Field(
93
- "resources", description="Directory containing resources"
94
- )
75
+ resources_dir: str = Field("resources", description="Directory containing resources")
95
76
  prompts_dir: str = Field("prompts", description="Directory containing prompts")
96
77
 
97
78
  # OpenTelemetry config
98
- opentelemetry_enabled: bool = Field(
99
- False, description="Enable OpenTelemetry tracing"
100
- )
101
- opentelemetry_default_exporter: str = Field(
102
- "console", description="Default OpenTelemetry exporter type"
79
+ opentelemetry_enabled: bool = Field(False, description="Enable OpenTelemetry tracing")
80
+ opentelemetry_default_exporter: str = Field("console", description="Default OpenTelemetry exporter type")
81
+ detailed_tracing: bool = Field(
82
+ False, description="Enable detailed tracing with input/output capture (may contain sensitive data)"
103
83
  )
104
84
 
105
85
  # Health check configuration
106
- health_check_enabled: bool = Field(
107
- False, description="Enable health check endpoint"
108
- )
86
+ health_check_enabled: bool = Field(False, description="Enable health check endpoint")
109
87
  health_check_path: str = Field("/health", description="Health check endpoint path")
110
88
  health_check_response: str = Field("OK", description="Health check response text")
111
89
 
@@ -116,9 +94,7 @@ class Settings(BaseSettings):
116
94
  )
117
95
 
118
96
  # Metrics configuration
119
- metrics_enabled: bool = Field(
120
- False, description="Enable Prometheus metrics endpoint"
121
- )
97
+ metrics_enabled: bool = Field(False, description="Enable Prometheus metrics endpoint")
122
98
  metrics_path: str = Field("/metrics", description="Metrics endpoint path")
123
99
 
124
100
 
@@ -166,7 +142,8 @@ def find_project_root(
166
142
  start_path: Path to start searching from (defaults to current directory)
167
143
 
168
144
  Returns:
169
- Tuple of (project_root, config_path) if a project is found, or (None, None) if not
145
+ Tuple of (project_root, config_path) if a project is found, or
146
+ (None, None) if not
170
147
  """
171
148
  config_path = find_config_path(start_path)
172
149
  if config_path:
@@ -195,20 +172,24 @@ def load_settings(project_path: str | Path) -> Settings:
195
172
  if env_file.exists():
196
173
  settings = Settings(_env_file=env_file)
197
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
+
198
181
  # Try to load JSON config file first
199
182
  json_config_path = project_path / "golf.json"
200
183
  if json_config_path.exists():
201
184
  return _load_json_settings(json_config_path, settings)
202
185
 
203
- # Fall back to TOML config file if JSON not found
204
- toml_config_path = project_path / "golf.toml"
205
- if toml_config_path.exists():
206
- console.print(
207
- "[yellow]Warning: Using .toml configuration is deprecated. Please migrate to .json format.[/yellow]"
208
- )
209
- return _load_toml_settings(toml_config_path, settings)
210
-
211
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
+
212
193
  return settings
213
194
 
214
195
 
@@ -225,15 +206,13 @@ def _load_json_settings(path: Path, settings: Settings) -> Settings:
225
206
  if hasattr(settings, key):
226
207
  setattr(settings, key, value)
227
208
 
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
+
228
215
  return settings
229
216
  except Exception as e:
230
- console.print(
231
- f"[bold red]Error loading JSON config from {path}: {e}[/bold red]"
232
- )
217
+ console.print(f"[bold red]Error loading JSON config from {path}: {e}[/bold red]")
233
218
  return settings
234
-
235
-
236
- def _load_toml_settings(path: Path, settings: Settings) -> Settings:
237
- """Load settings from a TOML file."""
238
-
239
- return settings