golf-mcp 0.1.10__py3-none-any.whl → 0.1.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 +1 -1
- golf/auth/__init__.py +38 -26
- golf/auth/api_key.py +16 -23
- golf/auth/helpers.py +68 -54
- golf/auth/oauth.py +340 -277
- golf/auth/provider.py +58 -53
- golf/cli/__init__.py +1 -1
- golf/cli/main.py +202 -82
- golf/commands/__init__.py +1 -1
- golf/commands/build.py +31 -25
- golf/commands/init.py +119 -80
- golf/commands/run.py +14 -13
- golf/core/__init__.py +1 -1
- golf/core/builder.py +478 -353
- golf/core/builder_auth.py +115 -107
- golf/core/builder_telemetry.py +12 -9
- golf/core/config.py +62 -46
- golf/core/parser.py +174 -136
- golf/core/telemetry.py +169 -69
- golf/core/transformer.py +53 -55
- golf/examples/__init__.py +0 -1
- golf/examples/api_key/pre_build.py +2 -2
- golf/examples/api_key/tools/issues/create.py +35 -36
- golf/examples/api_key/tools/issues/list.py +42 -37
- golf/examples/api_key/tools/repos/list.py +50 -29
- golf/examples/api_key/tools/search/code.py +50 -37
- golf/examples/api_key/tools/users/get.py +21 -20
- golf/examples/basic/pre_build.py +4 -4
- golf/examples/basic/prompts/welcome.py +6 -7
- golf/examples/basic/resources/current_time.py +10 -9
- golf/examples/basic/resources/info.py +6 -5
- golf/examples/basic/resources/weather/common.py +16 -10
- golf/examples/basic/resources/weather/current.py +15 -11
- golf/examples/basic/resources/weather/forecast.py +15 -11
- golf/examples/basic/tools/github_user.py +19 -21
- golf/examples/basic/tools/hello.py +10 -6
- golf/examples/basic/tools/payments/charge.py +34 -25
- golf/examples/basic/tools/payments/common.py +8 -6
- golf/examples/basic/tools/payments/refund.py +29 -25
- golf/telemetry/__init__.py +6 -6
- golf/telemetry/instrumentation.py +781 -276
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/METADATA +1 -1
- golf_mcp-0.1.12.dist-info/RECORD +55 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/WHEEL +1 -1
- golf_mcp-0.1.10.dist-info/RECORD +0 -55
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/top_level.txt +0 -0
golf/core/builder_auth.py
CHANGED
|
@@ -8,9 +8,16 @@ from golf.auth import get_auth_config
|
|
|
8
8
|
from golf.auth.api_key import get_api_key_config
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def generate_auth_code(
|
|
11
|
+
def generate_auth_code(
|
|
12
|
+
server_name: str,
|
|
13
|
+
host: str = "127.0.0.1",
|
|
14
|
+
port: int = 3000,
|
|
15
|
+
https: bool = False,
|
|
16
|
+
opentelemetry_enabled: bool = False,
|
|
17
|
+
transport: str = "streamable-http",
|
|
18
|
+
) -> dict:
|
|
12
19
|
"""Generate authentication components for the FastMCP app.
|
|
13
|
-
|
|
20
|
+
|
|
14
21
|
Returns a dictionary with:
|
|
15
22
|
- imports: List of import statements
|
|
16
23
|
- setup_code: Auth setup code (provider configuration, etc.)
|
|
@@ -20,19 +27,16 @@ def generate_auth_code(server_name: str, host: str = "127.0.0.1", port: int = 30
|
|
|
20
27
|
# Check for API key configuration first
|
|
21
28
|
api_key_config = get_api_key_config()
|
|
22
29
|
if api_key_config:
|
|
23
|
-
return generate_api_key_auth_components(
|
|
24
|
-
|
|
30
|
+
return generate_api_key_auth_components(
|
|
31
|
+
server_name, opentelemetry_enabled, transport
|
|
32
|
+
)
|
|
33
|
+
|
|
25
34
|
# Otherwise check for OAuth configuration
|
|
26
35
|
original_provider_config, required_scopes_from_config = get_auth_config()
|
|
27
|
-
|
|
36
|
+
|
|
28
37
|
if not original_provider_config:
|
|
29
38
|
# If no auth config, return empty components
|
|
30
|
-
return {
|
|
31
|
-
"imports": [],
|
|
32
|
-
"setup_code": [],
|
|
33
|
-
"fastmcp_args": {},
|
|
34
|
-
"has_auth": False
|
|
35
|
-
}
|
|
39
|
+
return {"imports": [], "setup_code": [], "fastmcp_args": {}, "has_auth": False}
|
|
36
40
|
|
|
37
41
|
# Build auth components
|
|
38
42
|
auth_imports = [
|
|
@@ -48,98 +52,107 @@ def generate_auth_code(server_name: str, host: str = "127.0.0.1", port: int = 30
|
|
|
48
52
|
setup_code_lines = []
|
|
49
53
|
|
|
50
54
|
# Code to determine runtime server address configuration
|
|
51
|
-
setup_code_lines.extend(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
)
|
|
65
71
|
|
|
66
72
|
# Code to load secrets from environment variables AT RUNTIME in server.py
|
|
67
|
-
setup_code_lines.extend(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
)
|
|
83
91
|
|
|
84
92
|
# Code to instantiate ProviderConfig using runtime-loaded secrets and other baked-in non-secrets
|
|
85
|
-
setup_code_lines.extend(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
)
|
|
110
120
|
|
|
111
121
|
# AuthSettings creation
|
|
112
|
-
setup_code_lines.extend(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
)
|
|
125
137
|
|
|
126
138
|
# FastMCP constructor arguments
|
|
127
|
-
fastmcp_args = {
|
|
128
|
-
"auth_server_provider": "auth_provider",
|
|
129
|
-
"auth": "auth_settings"
|
|
130
|
-
}
|
|
139
|
+
fastmcp_args = {"auth_server_provider": "auth_provider", "auth": "auth_settings"}
|
|
131
140
|
|
|
132
141
|
return {
|
|
133
142
|
"imports": auth_imports,
|
|
134
143
|
"setup_code": setup_code_lines,
|
|
135
144
|
"fastmcp_args": fastmcp_args,
|
|
136
|
-
"has_auth": True
|
|
145
|
+
"has_auth": True,
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
|
|
140
|
-
def generate_api_key_auth_components(
|
|
149
|
+
def generate_api_key_auth_components(
|
|
150
|
+
server_name: str,
|
|
151
|
+
opentelemetry_enabled: bool = False,
|
|
152
|
+
transport: str = "streamable-http",
|
|
153
|
+
) -> dict:
|
|
141
154
|
"""Generate authentication components for API key authentication.
|
|
142
|
-
|
|
155
|
+
|
|
143
156
|
Returns a dictionary with:
|
|
144
157
|
- imports: List of import statements
|
|
145
158
|
- setup_code: Auth setup code (middleware setup)
|
|
@@ -148,13 +161,8 @@ def generate_api_key_auth_components(server_name: str, opentelemetry_enabled: bo
|
|
|
148
161
|
"""
|
|
149
162
|
api_key_config = get_api_key_config()
|
|
150
163
|
if not api_key_config:
|
|
151
|
-
return {
|
|
152
|
-
|
|
153
|
-
"setup_code": [],
|
|
154
|
-
"fastmcp_args": {},
|
|
155
|
-
"has_auth": False
|
|
156
|
-
}
|
|
157
|
-
|
|
164
|
+
return {"imports": [], "setup_code": [], "fastmcp_args": {}, "has_auth": False}
|
|
165
|
+
|
|
158
166
|
auth_imports = [
|
|
159
167
|
"# API key authentication setup",
|
|
160
168
|
"from golf.auth.api_key import get_api_key_config, configure_api_key",
|
|
@@ -164,14 +172,14 @@ def generate_api_key_auth_components(server_name: str, opentelemetry_enabled: bo
|
|
|
164
172
|
"from starlette.responses import JSONResponse",
|
|
165
173
|
"import os",
|
|
166
174
|
]
|
|
167
|
-
|
|
175
|
+
|
|
168
176
|
setup_code_lines = [
|
|
169
177
|
"# Recreate API key configuration from pre_build.py",
|
|
170
|
-
|
|
178
|
+
"configure_api_key(",
|
|
171
179
|
f" header_name={repr(api_key_config.header_name)},",
|
|
172
180
|
f" header_prefix={repr(api_key_config.header_prefix)},",
|
|
173
181
|
f" required={repr(api_key_config.required)}",
|
|
174
|
-
|
|
182
|
+
")",
|
|
175
183
|
"",
|
|
176
184
|
"# Simplified API key middleware that validates presence",
|
|
177
185
|
"class ApiKeyMiddleware(BaseHTTPMiddleware):",
|
|
@@ -217,15 +225,15 @@ def generate_api_key_auth_components(server_name: str, opentelemetry_enabled: bo
|
|
|
217
225
|
" return await call_next(request)",
|
|
218
226
|
"",
|
|
219
227
|
]
|
|
220
|
-
|
|
228
|
+
|
|
221
229
|
# API key auth is handled via middleware, not FastMCP constructor args
|
|
222
230
|
fastmcp_args = {}
|
|
223
|
-
|
|
231
|
+
|
|
224
232
|
return {
|
|
225
233
|
"imports": auth_imports,
|
|
226
234
|
"setup_code": setup_code_lines,
|
|
227
235
|
"fastmcp_args": fastmcp_args,
|
|
228
|
-
"has_auth": True
|
|
236
|
+
"has_auth": True,
|
|
229
237
|
}
|
|
230
238
|
|
|
231
239
|
|
|
@@ -237,26 +245,26 @@ def generate_auth_routes() -> str:
|
|
|
237
245
|
api_key_config = get_api_key_config()
|
|
238
246
|
if api_key_config:
|
|
239
247
|
return ""
|
|
240
|
-
|
|
241
|
-
provider_config, _ = get_auth_config()
|
|
248
|
+
|
|
249
|
+
provider_config, _ = get_auth_config() # Used to check if auth is enabled generally
|
|
242
250
|
if not provider_config:
|
|
243
251
|
return ""
|
|
244
|
-
|
|
252
|
+
|
|
245
253
|
auth_routes_list = [
|
|
246
254
|
"",
|
|
247
255
|
"# Auth-specific routes, using the 'auth_provider' instance defined in the auth setup code",
|
|
248
256
|
"@mcp.custom_route('/auth/callback', methods=['GET'])",
|
|
249
257
|
"async def oauth_callback(request):",
|
|
250
258
|
" # create_callback_handler is imported in the auth setup code block",
|
|
251
|
-
" handler = create_callback_handler(auth_provider)",
|
|
259
|
+
" handler = create_callback_handler(auth_provider)",
|
|
252
260
|
" return await handler(request)",
|
|
253
261
|
"",
|
|
254
262
|
"@mcp.custom_route('/login', methods=['GET'])",
|
|
255
263
|
"async def login(request):",
|
|
256
264
|
" from starlette.responses import RedirectResponse",
|
|
257
265
|
" import urllib.parse",
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
' default_redirect_uri = urllib.parse.quote_plus("http://localhost:5173/callback")',
|
|
267
|
+
' authorize_url = f"/mcp/auth/authorize?client_id=default&response_type=code&redirect_uri={default_redirect_uri}"',
|
|
260
268
|
" return RedirectResponse(authorize_url)",
|
|
261
269
|
"",
|
|
262
270
|
"@mcp.custom_route('/auth-error', methods=['GET'])",
|
|
@@ -264,7 +272,7 @@ def generate_auth_routes() -> str:
|
|
|
264
272
|
" from starlette.responses import HTMLResponse",
|
|
265
273
|
" error = request.query_params.get('error', 'unknown_error')",
|
|
266
274
|
" error_desc = request.query_params.get('error_description', 'An authentication error occurred')",
|
|
267
|
-
|
|
275
|
+
' html_content = f"""',
|
|
268
276
|
" <!DOCTYPE html>",
|
|
269
277
|
" <html><head><title>Authentication Error</title>",
|
|
270
278
|
" <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>",
|
|
@@ -274,4 +282,4 @@ def generate_auth_routes() -> str:
|
|
|
274
282
|
" return HTMLResponse(content=html_content)",
|
|
275
283
|
"",
|
|
276
284
|
]
|
|
277
|
-
return "\n".join(auth_routes_list)
|
|
285
|
+
return "\n".join(auth_routes_list)
|
golf/core/builder_telemetry.py
CHANGED
|
@@ -4,9 +4,10 @@ This module provides functions for generating OpenTelemetry initialization
|
|
|
4
4
|
and instrumentation code for FastMCP servers built with GolfMCP.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
def generate_telemetry_imports() -> list[str]:
|
|
8
9
|
"""Generate import statements for telemetry instrumentation.
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
Returns:
|
|
11
12
|
List of import statements for telemetry
|
|
12
13
|
"""
|
|
@@ -20,16 +21,17 @@ def generate_telemetry_imports() -> list[str]:
|
|
|
20
21
|
")",
|
|
21
22
|
]
|
|
22
23
|
|
|
24
|
+
|
|
23
25
|
def generate_component_registration_with_telemetry(
|
|
24
26
|
component_type: str,
|
|
25
27
|
component_name: str,
|
|
26
28
|
module_path: str,
|
|
27
29
|
entry_function: str,
|
|
28
30
|
docstring: str = "",
|
|
29
|
-
uri_template: str = None
|
|
31
|
+
uri_template: str = None,
|
|
30
32
|
) -> str:
|
|
31
33
|
"""Generate component registration code with telemetry instrumentation.
|
|
32
|
-
|
|
34
|
+
|
|
33
35
|
Args:
|
|
34
36
|
component_type: Type of component ('tool', 'resource', 'prompt')
|
|
35
37
|
component_name: Name of the component
|
|
@@ -37,31 +39,32 @@ def generate_component_registration_with_telemetry(
|
|
|
37
39
|
entry_function: Entry function name
|
|
38
40
|
docstring: Component description
|
|
39
41
|
uri_template: URI template for resources (optional)
|
|
40
|
-
|
|
42
|
+
|
|
41
43
|
Returns:
|
|
42
44
|
Python code string for registering the component with instrumentation
|
|
43
45
|
"""
|
|
44
46
|
func_ref = f"{module_path}.{entry_function}"
|
|
45
47
|
escaped_docstring = docstring.replace('"', '\\"') if docstring else ""
|
|
46
|
-
|
|
48
|
+
|
|
47
49
|
if component_type == "tool":
|
|
48
50
|
wrapped_func = f"instrument_tool({func_ref}, '{component_name}')"
|
|
49
51
|
return f'mcp.add_tool({wrapped_func}, name="{component_name}", description="{escaped_docstring}")'
|
|
50
|
-
|
|
52
|
+
|
|
51
53
|
elif component_type == "resource":
|
|
52
54
|
wrapped_func = f"instrument_resource({func_ref}, '{uri_template}')"
|
|
53
55
|
return f'mcp.add_resource_fn({wrapped_func}, uri="{uri_template}", name="{component_name}", description="{escaped_docstring}")'
|
|
54
|
-
|
|
56
|
+
|
|
55
57
|
elif component_type == "prompt":
|
|
56
58
|
wrapped_func = f"instrument_prompt({func_ref}, '{component_name}')"
|
|
57
59
|
return f'mcp.add_prompt({wrapped_func}, name="{component_name}", description="{escaped_docstring}")'
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
else:
|
|
60
62
|
raise ValueError(f"Unknown component type: {component_type}")
|
|
61
63
|
|
|
64
|
+
|
|
62
65
|
def get_otel_dependencies() -> list[str]:
|
|
63
66
|
"""Get list of OpenTelemetry dependencies to add to pyproject.toml.
|
|
64
|
-
|
|
67
|
+
|
|
65
68
|
Returns:
|
|
66
69
|
List of package requirements strings
|
|
67
70
|
"""
|
golf/core/config.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Configuration management for GolfMCP."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, Field, field_validator
|
|
7
7
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
@@ -16,14 +16,14 @@ class AuthConfig(BaseModel):
|
|
|
16
16
|
provider: str = Field(
|
|
17
17
|
..., description="Authentication provider (e.g., 'jwks', 'google', 'github')"
|
|
18
18
|
)
|
|
19
|
-
scopes:
|
|
20
|
-
client_id_env:
|
|
19
|
+
scopes: list[str] = Field(default_factory=list, description="Required OAuth scopes")
|
|
20
|
+
client_id_env: str | None = Field(
|
|
21
21
|
None, description="Environment variable name for client ID"
|
|
22
22
|
)
|
|
23
|
-
client_secret_env:
|
|
23
|
+
client_secret_env: str | None = Field(
|
|
24
24
|
None, description="Environment variable name for client secret"
|
|
25
25
|
)
|
|
26
|
-
redirect_uri:
|
|
26
|
+
redirect_uri: str | None = Field(
|
|
27
27
|
None, description="OAuth redirect URI (defaults to localhost callback)"
|
|
28
28
|
)
|
|
29
29
|
|
|
@@ -44,7 +44,7 @@ class DeployConfig(BaseModel):
|
|
|
44
44
|
"""Deployment configuration."""
|
|
45
45
|
|
|
46
46
|
default: str = Field("vercel", description="Default deployment target")
|
|
47
|
-
options:
|
|
47
|
+
options: dict[str, Any] = Field(
|
|
48
48
|
default_factory=dict, description="Target-specific options"
|
|
49
49
|
)
|
|
50
50
|
|
|
@@ -61,80 +61,91 @@ class Settings(BaseSettings):
|
|
|
61
61
|
|
|
62
62
|
# Project metadata
|
|
63
63
|
name: str = Field("GolfMCP Project", description="FastMCP instance name")
|
|
64
|
-
description:
|
|
65
|
-
|
|
64
|
+
description: str | None = Field(None, description="Project description")
|
|
65
|
+
|
|
66
66
|
# Build settings
|
|
67
67
|
output_dir: str = Field("build", description="Build artifact folder")
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
# Server settings
|
|
70
70
|
host: str = Field("127.0.0.1", description="Server host")
|
|
71
71
|
port: int = Field(3000, description="Server port")
|
|
72
|
-
transport: str = Field(
|
|
73
|
-
|
|
72
|
+
transport: str = Field(
|
|
73
|
+
"streamable-http",
|
|
74
|
+
description="Transport protocol (streamable-http, sse, stdio)",
|
|
75
|
+
)
|
|
76
|
+
|
|
74
77
|
# Auth settings
|
|
75
|
-
auth:
|
|
78
|
+
auth: str | AuthConfig | None = Field(
|
|
76
79
|
None, description="Authentication configuration or URI"
|
|
77
80
|
)
|
|
78
|
-
|
|
81
|
+
|
|
79
82
|
# Deploy settings
|
|
80
83
|
deploy: DeployConfig = Field(
|
|
81
84
|
default_factory=DeployConfig, description="Deployment configuration"
|
|
82
85
|
)
|
|
83
|
-
|
|
86
|
+
|
|
84
87
|
# Feature flags
|
|
85
88
|
telemetry: bool = Field(True, description="Enable anonymous telemetry")
|
|
86
|
-
|
|
89
|
+
|
|
87
90
|
# Project paths
|
|
88
91
|
tools_dir: str = Field("tools", description="Directory containing tools")
|
|
89
|
-
resources_dir: str = Field(
|
|
92
|
+
resources_dir: str = Field(
|
|
93
|
+
"resources", description="Directory containing resources"
|
|
94
|
+
)
|
|
90
95
|
prompts_dir: str = Field("prompts", description="Directory containing prompts")
|
|
91
96
|
|
|
92
97
|
# OpenTelemetry config
|
|
93
|
-
opentelemetry_enabled: bool = Field(
|
|
94
|
-
|
|
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"
|
|
103
|
+
)
|
|
95
104
|
|
|
96
105
|
|
|
97
|
-
def find_config_path(start_path:
|
|
106
|
+
def find_config_path(start_path: Path | None = None) -> Path | None:
|
|
98
107
|
"""Find the golf config file by searching upwards from the given path.
|
|
99
|
-
|
|
108
|
+
|
|
100
109
|
Args:
|
|
101
110
|
start_path: Path to start searching from (defaults to current directory)
|
|
102
|
-
|
|
111
|
+
|
|
103
112
|
Returns:
|
|
104
113
|
Path to the config file if found, None otherwise
|
|
105
114
|
"""
|
|
106
115
|
if start_path is None:
|
|
107
116
|
start_path = Path.cwd()
|
|
108
|
-
|
|
117
|
+
|
|
109
118
|
current = start_path.absolute()
|
|
110
|
-
|
|
119
|
+
|
|
111
120
|
# Don't search above the home directory
|
|
112
121
|
home = Path.home().absolute()
|
|
113
|
-
|
|
122
|
+
|
|
114
123
|
while current != current.parent and current != home:
|
|
115
124
|
# Check for JSON config first (preferred)
|
|
116
125
|
json_config = current / "golf.json"
|
|
117
126
|
if json_config.exists():
|
|
118
127
|
return json_config
|
|
119
|
-
|
|
128
|
+
|
|
120
129
|
# Fall back to TOML config
|
|
121
130
|
toml_config = current / "golf.toml"
|
|
122
131
|
if toml_config.exists():
|
|
123
132
|
return toml_config
|
|
124
|
-
|
|
133
|
+
|
|
125
134
|
current = current.parent
|
|
126
|
-
|
|
135
|
+
|
|
127
136
|
return None
|
|
128
137
|
|
|
129
138
|
|
|
130
|
-
def find_project_root(
|
|
139
|
+
def find_project_root(
|
|
140
|
+
start_path: Path | None = None,
|
|
141
|
+
) -> tuple[Path | None, Path | None]:
|
|
131
142
|
"""Find a GolfMCP project root by searching for a config file.
|
|
132
|
-
|
|
143
|
+
|
|
133
144
|
This is the central project discovery function that should be used by all commands.
|
|
134
|
-
|
|
145
|
+
|
|
135
146
|
Args:
|
|
136
147
|
start_path: Path to start searching from (defaults to current directory)
|
|
137
|
-
|
|
148
|
+
|
|
138
149
|
Returns:
|
|
139
150
|
Tuple of (project_root, config_path) if a project is found, or (None, None) if not
|
|
140
151
|
"""
|
|
@@ -144,38 +155,40 @@ def find_project_root(start_path: Optional[Path] = None) -> Tuple[Optional[Path]
|
|
|
144
155
|
return None, None
|
|
145
156
|
|
|
146
157
|
|
|
147
|
-
def load_settings(project_path:
|
|
158
|
+
def load_settings(project_path: str | Path) -> Settings:
|
|
148
159
|
"""Load settings from a project directory.
|
|
149
|
-
|
|
160
|
+
|
|
150
161
|
Args:
|
|
151
162
|
project_path: Path to the project directory
|
|
152
|
-
|
|
163
|
+
|
|
153
164
|
Returns:
|
|
154
165
|
Settings object with values loaded from config files
|
|
155
166
|
"""
|
|
156
167
|
# Convert to Path if needed
|
|
157
168
|
if isinstance(project_path, str):
|
|
158
169
|
project_path = Path(project_path)
|
|
159
|
-
|
|
170
|
+
|
|
160
171
|
# Create default settings
|
|
161
172
|
settings = Settings()
|
|
162
|
-
|
|
173
|
+
|
|
163
174
|
# Check for .env file
|
|
164
175
|
env_file = project_path / ".env"
|
|
165
176
|
if env_file.exists():
|
|
166
177
|
settings = Settings(_env_file=env_file)
|
|
167
|
-
|
|
178
|
+
|
|
168
179
|
# Try to load JSON config file first
|
|
169
180
|
json_config_path = project_path / "golf.json"
|
|
170
181
|
if json_config_path.exists():
|
|
171
182
|
return _load_json_settings(json_config_path, settings)
|
|
172
|
-
|
|
183
|
+
|
|
173
184
|
# Fall back to TOML config file if JSON not found
|
|
174
185
|
toml_config_path = project_path / "golf.toml"
|
|
175
186
|
if toml_config_path.exists():
|
|
176
|
-
console.print(
|
|
187
|
+
console.print(
|
|
188
|
+
"[yellow]Warning: Using .toml configuration is deprecated. Please migrate to .json format.[/yellow]"
|
|
189
|
+
)
|
|
177
190
|
return _load_toml_settings(toml_config_path, settings)
|
|
178
|
-
|
|
191
|
+
|
|
179
192
|
# No config file found, use defaults
|
|
180
193
|
return settings
|
|
181
194
|
|
|
@@ -184,21 +197,24 @@ def _load_json_settings(path: Path, settings: Settings) -> Settings:
|
|
|
184
197
|
"""Load settings from a JSON file."""
|
|
185
198
|
try:
|
|
186
199
|
import json
|
|
187
|
-
|
|
200
|
+
|
|
201
|
+
with open(path) as f:
|
|
188
202
|
config_data = json.load(f)
|
|
189
|
-
|
|
203
|
+
|
|
190
204
|
# Update settings from config data
|
|
191
205
|
for key, value in config_data.items():
|
|
192
206
|
if hasattr(settings, key):
|
|
193
207
|
setattr(settings, key, value)
|
|
194
|
-
|
|
208
|
+
|
|
195
209
|
return settings
|
|
196
210
|
except Exception as e:
|
|
197
|
-
console.print(
|
|
211
|
+
console.print(
|
|
212
|
+
f"[bold red]Error loading JSON config from {path}: {e}[/bold red]"
|
|
213
|
+
)
|
|
198
214
|
return settings
|
|
199
215
|
|
|
200
216
|
|
|
201
217
|
def _load_toml_settings(path: Path, settings: Settings) -> Settings:
|
|
202
218
|
"""Load settings from a TOML file."""
|
|
203
|
-
|
|
204
|
-
return settings
|
|
219
|
+
|
|
220
|
+
return settings
|