golf-mcp 0.1.19__py3-none-any.whl → 0.2.0__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 +9 -1
- golf/_endpoints.py +6 -0
- golf/_endpoints_fallback.py +10 -0
- golf/auth/__init__.py +188 -84
- golf/auth/api_key.py +6 -14
- golf/auth/factory.py +333 -0
- golf/auth/helpers.py +12 -42
- golf/auth/providers.py +396 -0
- golf/auth/registry.py +256 -0
- golf/cli/branding.py +192 -0
- golf/cli/main.py +28 -69
- golf/commands/__init__.py +2 -0
- golf/commands/build.py +4 -7
- golf/commands/init.py +30 -53
- golf/commands/run.py +50 -20
- golf/core/builder.py +356 -412
- golf/core/builder_auth.py +63 -144
- golf/core/builder_telemetry.py +26 -3
- golf/core/config.py +38 -59
- golf/core/parser.py +132 -139
- golf/core/platform.py +12 -10
- golf/core/telemetry.py +11 -19
- golf/core/transformer.py +38 -15
- golf/examples/__pycache__/__init__.cpython-311.pyc +0 -0
- golf/examples/basic/.coverage +0 -0
- golf/examples/basic/.env.example +8 -4
- golf/examples/basic/README.md +117 -45
- golf/examples/basic/__pycache__/auth.cpython-311.pyc +0 -0
- golf/examples/basic/auth.py +76 -0
- golf/examples/basic/golf.json +2 -5
- golf/examples/basic/htmlcov/.gitignore +2 -0
- golf/examples/basic/htmlcov/class_index.html +547 -0
- golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +733 -0
- golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
- golf/examples/basic/htmlcov/function_index.html +2091 -0
- golf/examples/basic/htmlcov/index.html +349 -0
- golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
- golf/examples/basic/htmlcov/status.json +1 -0
- golf/examples/basic/htmlcov/style_cb_8e611ae1.css +337 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +323 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +170 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +430 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +288 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +493 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +353 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +120 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +1535 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +289 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +476 -0
- golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +97 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +102 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +178 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +387 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +222 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +106 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +107 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +306 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +329 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +1471 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +186 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +315 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +1149 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +279 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +589 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +286 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +107 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +417 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +150 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +267 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +318 -0
- golf/examples/basic/prompts/__pycache__/welcome.cpython-311.pyc +0 -0
- golf/examples/basic/prompts/welcome.py +3 -5
- golf/examples/basic/resources/__pycache__/current_time.cpython-311.pyc +0 -0
- golf/examples/basic/resources/__pycache__/info.cpython-311.pyc +0 -0
- golf/examples/basic/resources/current_time.py +5 -13
- golf/examples/basic/resources/weather/__pycache__/common.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/current.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/forecast.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/city.py +46 -0
- golf/examples/basic/resources/weather/common.py +4 -11
- golf/examples/basic/resources/weather/current.py +5 -5
- golf/examples/basic/resources/weather/forecast.py +5 -5
- golf/examples/basic/tools/__pycache__/calculator.cpython-311.pyc +0 -0
- golf/examples/basic/tools/calculator.py +94 -0
- golf/examples/basic/tools/say/__pycache__/hello.cpython-311.pyc +0 -0
- golf/examples/basic/tools/say/hello.py +65 -0
- golf/metrics/collector.py +100 -19
- golf/telemetry/__init__.py +4 -0
- golf/telemetry/instrumentation.py +496 -174
- golf/utilities/__init__.py +12 -0
- golf/utilities/context.py +53 -0
- golf/utilities/elicitation.py +170 -0
- golf/utilities/sampling.py +221 -0
- {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/METADATA +56 -110
- golf_mcp-0.2.0.dist-info/RECORD +110 -0
- golf/auth/oauth.py +0 -861
- golf/auth/provider.py +0 -115
- golf/examples/api_key/.env +0 -2
- golf/examples/api_key/.env.example +0 -1
- golf/examples/api_key/README.md +0 -84
- golf/examples/api_key/golf.json +0 -8
- golf/examples/api_key/pre_build.py +0 -11
- golf/examples/api_key/tools/issues/create.py +0 -93
- golf/examples/api_key/tools/issues/list.py +0 -92
- golf/examples/api_key/tools/repos/list.py +0 -111
- golf/examples/api_key/tools/search/code.py +0 -106
- golf/examples/api_key/tools/users/get.py +0 -82
- golf/examples/basic/.env +0 -5
- golf/examples/basic/pre_build.py +0 -28
- golf/examples/basic/tools/github_user.py +0 -65
- golf/examples/basic/tools/hello.py +0 -34
- golf/examples/basic/tools/payments/charge.py +0 -70
- golf/examples/basic/tools/payments/common.py +0 -36
- golf/examples/basic/tools/payments/refund.py +0 -61
- golf_mcp-0.1.19.dist-info/RECORD +0 -60
- {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/top_level.txt +0 -0
golf/core/builder_auth.py
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
"""Authentication integration for the
|
|
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 = "
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
45
|
-
"from
|
|
46
|
-
"from golf.auth.
|
|
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",
|
|
50
56
|
]
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 = {"
|
|
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
|
|
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',
|
|
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
|
|
247
|
-
|
|
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
|
-
|
|
255
|
-
if not
|
|
193
|
+
# Check if auth is configured
|
|
194
|
+
if not is_auth_configured():
|
|
256
195
|
return ""
|
|
257
196
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
+
"""
|
golf/core/builder_telemetry.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
54
|
+
output_dir: str = Field("dist", description="Build artifact folder")
|
|
68
55
|
|
|
69
56
|
# Server settings
|
|
70
|
-
host: str = Field("
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|