golf-mcp 0.1.20__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 +355 -414
- 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 +484 -178
- 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.20.dist-info → golf_mcp-0.2.0.dist-info}/METADATA +51 -104
- 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.20.dist-info/RECORD +0 -60
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/top_level.txt +0 -0
golf/core/builder.py
CHANGED
|
@@ -9,15 +9,14 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
import black
|
|
11
11
|
from rich.console import Console
|
|
12
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
13
12
|
|
|
14
|
-
from golf.auth import
|
|
13
|
+
from golf.auth import is_auth_configured
|
|
15
14
|
from golf.auth.api_key import get_api_key_config
|
|
16
15
|
from golf.core.builder_auth import generate_auth_code, generate_auth_routes
|
|
17
16
|
from golf.core.builder_telemetry import (
|
|
18
17
|
generate_telemetry_imports,
|
|
19
|
-
get_otel_dependencies,
|
|
20
18
|
)
|
|
19
|
+
from golf.cli.branding import create_build_header, get_status_text, STATUS_ICONS, GOLF_BLUE
|
|
21
20
|
from golf.core.config import Settings
|
|
22
21
|
from golf.core.parser import (
|
|
23
22
|
ComponentType,
|
|
@@ -109,9 +108,7 @@ class ManifestBuilder:
|
|
|
109
108
|
"""Process all resource components and add them to the manifest."""
|
|
110
109
|
for component in self.components[ComponentType.RESOURCE]:
|
|
111
110
|
if not component.uri_template:
|
|
112
|
-
console.print(
|
|
113
|
-
f"[yellow]Warning: Resource {component.name} has no URI template[/yellow]"
|
|
114
|
-
)
|
|
111
|
+
console.print(f"[yellow]Warning: Resource {component.name} has no URI template[/yellow]")
|
|
115
112
|
continue
|
|
116
113
|
|
|
117
114
|
resource_schema = {
|
|
@@ -127,7 +124,8 @@ class ManifestBuilder:
|
|
|
127
124
|
def _process_prompts(self) -> None:
|
|
128
125
|
"""Process all prompt components and add them to the manifest."""
|
|
129
126
|
for component in self.components[ComponentType.PROMPT]:
|
|
130
|
-
# For prompts, the handler will have to load the module and execute
|
|
127
|
+
# For prompts, the handler will have to load the module and execute
|
|
128
|
+
# the run function
|
|
131
129
|
# to get the actual messages, so we just register it by name
|
|
132
130
|
prompt_schema = {
|
|
133
131
|
"name": component.name,
|
|
@@ -188,9 +186,7 @@ def build_manifest(project_path: Path, settings: Settings) -> dict[str, Any]:
|
|
|
188
186
|
return builder.build()
|
|
189
187
|
|
|
190
188
|
|
|
191
|
-
def compute_manifest_diff(
|
|
192
|
-
old_manifest: dict[str, Any], new_manifest: dict[str, Any]
|
|
193
|
-
) -> dict[str, Any]:
|
|
189
|
+
def compute_manifest_diff(old_manifest: dict[str, Any], new_manifest: dict[str, Any]) -> dict[str, Any]:
|
|
194
190
|
"""Compute the difference between two manifests.
|
|
195
191
|
|
|
196
192
|
Args:
|
|
@@ -221,11 +217,7 @@ def compute_manifest_diff(
|
|
|
221
217
|
if new_tool["name"] in old_tools:
|
|
222
218
|
# Find the corresponding old tool
|
|
223
219
|
old_tool = next(
|
|
224
|
-
(
|
|
225
|
-
t
|
|
226
|
-
for t in old_manifest.get("tools", [])
|
|
227
|
-
if t["name"] == new_tool["name"]
|
|
228
|
-
),
|
|
220
|
+
(t for t in old_manifest.get("tools", []) if t["name"] == new_tool["name"]),
|
|
229
221
|
None,
|
|
230
222
|
)
|
|
231
223
|
if old_tool and json.dumps(old_tool) != json.dumps(new_tool):
|
|
@@ -242,11 +234,7 @@ def compute_manifest_diff(
|
|
|
242
234
|
if new_resource["name"] in old_resources:
|
|
243
235
|
# Find the corresponding old resource
|
|
244
236
|
old_resource = next(
|
|
245
|
-
(
|
|
246
|
-
r
|
|
247
|
-
for r in old_manifest.get("resources", [])
|
|
248
|
-
if r["name"] == new_resource["name"]
|
|
249
|
-
),
|
|
237
|
+
(r for r in old_manifest.get("resources", []) if r["name"] == new_resource["name"]),
|
|
250
238
|
None,
|
|
251
239
|
)
|
|
252
240
|
if old_resource and json.dumps(old_resource) != json.dumps(new_resource):
|
|
@@ -263,11 +251,7 @@ def compute_manifest_diff(
|
|
|
263
251
|
if new_prompt["name"] in old_prompts:
|
|
264
252
|
# Find the corresponding old prompt
|
|
265
253
|
old_prompt = next(
|
|
266
|
-
(
|
|
267
|
-
p
|
|
268
|
-
for p in old_manifest.get("prompts", [])
|
|
269
|
-
if p["name"] == new_prompt["name"]
|
|
270
|
-
),
|
|
254
|
+
(p for p in old_manifest.get("prompts", []) if p["name"] == new_prompt["name"]),
|
|
271
255
|
None,
|
|
272
256
|
)
|
|
273
257
|
if old_prompt and json.dumps(old_prompt) != json.dumps(new_prompt):
|
|
@@ -320,7 +304,7 @@ class CodeGenerator:
|
|
|
320
304
|
self.copy_env = copy_env
|
|
321
305
|
self.components = {}
|
|
322
306
|
self.manifest = {}
|
|
323
|
-
self.
|
|
307
|
+
self.shared_files = {}
|
|
324
308
|
self.import_map = {}
|
|
325
309
|
|
|
326
310
|
def generate(self) -> None:
|
|
@@ -330,42 +314,39 @@ class CodeGenerator:
|
|
|
330
314
|
self.components = parse_project(self.project_path)
|
|
331
315
|
self.manifest = build_manifest(self.project_path, self.settings)
|
|
332
316
|
|
|
333
|
-
# Find
|
|
334
|
-
|
|
335
|
-
|
|
317
|
+
# Find shared Python files and build import map
|
|
318
|
+
from golf.core.parser import parse_shared_files
|
|
319
|
+
|
|
320
|
+
self.shared_files = parse_shared_files(self.project_path)
|
|
321
|
+
self.import_map = build_import_map(self.project_path, self.shared_files)
|
|
336
322
|
|
|
337
323
|
# Create output directory structure
|
|
338
324
|
with console.status("Creating directory structure..."):
|
|
339
325
|
self._create_directory_structure()
|
|
340
326
|
|
|
341
327
|
# Generate code for all components
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
("tools", self._generate_tools),
|
|
349
|
-
("resources", self._generate_resources),
|
|
350
|
-
("prompts", self._generate_prompts),
|
|
351
|
-
("server entry point", self._generate_server),
|
|
352
|
-
]
|
|
328
|
+
tasks = [
|
|
329
|
+
("Generating tools", self._generate_tools),
|
|
330
|
+
("Generating resources", self._generate_resources),
|
|
331
|
+
("Generating prompts", self._generate_prompts),
|
|
332
|
+
("Generating server entry point", self._generate_server),
|
|
333
|
+
]
|
|
353
334
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
progress.update(task, completed=1)
|
|
335
|
+
for description, func in tasks:
|
|
336
|
+
console.print(get_status_text("generating", description))
|
|
337
|
+
func()
|
|
358
338
|
|
|
359
339
|
# Get relative path for display
|
|
360
340
|
try:
|
|
361
341
|
output_dir_display = self.output_dir.relative_to(Path.cwd())
|
|
362
|
-
except ValueError:
|
|
342
|
+
except (ValueError, FileNotFoundError, OSError):
|
|
343
|
+
# ValueError: paths don't have a common base
|
|
344
|
+
# FileNotFoundError/OSError: current directory was deleted
|
|
363
345
|
output_dir_display = self.output_dir
|
|
364
346
|
|
|
365
347
|
# Show success message with output directory
|
|
366
|
-
console.print(
|
|
367
|
-
|
|
368
|
-
)
|
|
348
|
+
console.print()
|
|
349
|
+
console.print(get_status_text("success", f"Build completed successfully in {output_dir_display}"))
|
|
369
350
|
|
|
370
351
|
def _create_directory_structure(self) -> None:
|
|
371
352
|
"""Create the output directory structure"""
|
|
@@ -380,19 +361,20 @@ class CodeGenerator:
|
|
|
380
361
|
|
|
381
362
|
for directory in dirs:
|
|
382
363
|
directory.mkdir(parents=True, exist_ok=True)
|
|
383
|
-
# Process
|
|
384
|
-
self.
|
|
364
|
+
# Process shared files directly in the components directory
|
|
365
|
+
self._process_shared_files()
|
|
385
366
|
|
|
386
|
-
def
|
|
387
|
-
"""Process and transform
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
367
|
+
def _process_shared_files(self) -> None:
|
|
368
|
+
"""Process and transform shared Python files in the components directory
|
|
369
|
+
structure."""
|
|
370
|
+
# Process all shared files
|
|
371
|
+
for module_path_str, shared_file in self.shared_files.items():
|
|
372
|
+
# Convert module path to Path object (e.g., "tools/weather/helpers")
|
|
373
|
+
module_path = Path(module_path_str)
|
|
392
374
|
|
|
393
375
|
# Determine the component type
|
|
394
376
|
component_type = None
|
|
395
|
-
for part in
|
|
377
|
+
for part in module_path.parts:
|
|
396
378
|
if part in ["tools", "resources", "prompts"]:
|
|
397
379
|
component_type = part
|
|
398
380
|
break
|
|
@@ -401,16 +383,14 @@ class CodeGenerator:
|
|
|
401
383
|
continue
|
|
402
384
|
|
|
403
385
|
# Calculate target directory in components structure
|
|
404
|
-
rel_to_component =
|
|
405
|
-
target_dir =
|
|
406
|
-
self.output_dir / "components" / component_type / rel_to_component
|
|
407
|
-
)
|
|
386
|
+
rel_to_component = module_path.relative_to(component_type)
|
|
387
|
+
target_dir = self.output_dir / "components" / component_type / rel_to_component.parent
|
|
408
388
|
|
|
409
389
|
# Create directory if it doesn't exist
|
|
410
390
|
target_dir.mkdir(parents=True, exist_ok=True)
|
|
411
391
|
|
|
412
|
-
# Create the
|
|
413
|
-
target_file = target_dir /
|
|
392
|
+
# Create the shared file in the target directory (preserve original filename)
|
|
393
|
+
target_file = target_dir / shared_file.name
|
|
414
394
|
|
|
415
395
|
# Use transformer to process the file
|
|
416
396
|
transform_component(
|
|
@@ -418,7 +398,7 @@ class CodeGenerator:
|
|
|
418
398
|
output_file=target_file,
|
|
419
399
|
project_path=self.project_path,
|
|
420
400
|
import_map=self.import_map,
|
|
421
|
-
source_file=
|
|
401
|
+
source_file=shared_file,
|
|
422
402
|
)
|
|
423
403
|
|
|
424
404
|
def _generate_tools(self) -> None:
|
|
@@ -429,9 +409,7 @@ class CodeGenerator:
|
|
|
429
409
|
# Get the tool directory structure
|
|
430
410
|
rel_path = Path(tool.file_path).relative_to(self.project_path)
|
|
431
411
|
if not rel_path.is_relative_to(Path(self.settings.tools_dir)):
|
|
432
|
-
console.print(
|
|
433
|
-
f"[yellow]Warning: Tool {tool.name} is not in the tools directory[/yellow]"
|
|
434
|
-
)
|
|
412
|
+
console.print(f"[yellow]Warning: Tool {tool.name} is not in the tools directory[/yellow]")
|
|
435
413
|
continue
|
|
436
414
|
|
|
437
415
|
try:
|
|
@@ -455,9 +433,7 @@ class CodeGenerator:
|
|
|
455
433
|
# Get the resource directory structure
|
|
456
434
|
rel_path = Path(resource.file_path).relative_to(self.project_path)
|
|
457
435
|
if not rel_path.is_relative_to(Path(self.settings.resources_dir)):
|
|
458
|
-
console.print(
|
|
459
|
-
f"[yellow]Warning: Resource {resource.name} is not in the resources directory[/yellow]"
|
|
460
|
-
)
|
|
436
|
+
console.print(f"[yellow]Warning: Resource {resource.name} is not in the resources directory[/yellow]")
|
|
461
437
|
continue
|
|
462
438
|
|
|
463
439
|
try:
|
|
@@ -471,9 +447,7 @@ class CodeGenerator:
|
|
|
471
447
|
|
|
472
448
|
# Create the resource file
|
|
473
449
|
output_file = resource_dir / rel_path.name
|
|
474
|
-
transform_component(
|
|
475
|
-
resource, output_file, self.project_path, self.import_map
|
|
476
|
-
)
|
|
450
|
+
transform_component(resource, output_file, self.project_path, self.import_map)
|
|
477
451
|
|
|
478
452
|
def _generate_prompts(self) -> None:
|
|
479
453
|
"""Generate code for all prompts."""
|
|
@@ -483,9 +457,7 @@ class CodeGenerator:
|
|
|
483
457
|
# Get the prompt directory structure
|
|
484
458
|
rel_path = Path(prompt.file_path).relative_to(self.project_path)
|
|
485
459
|
if not rel_path.is_relative_to(Path(self.settings.prompts_dir)):
|
|
486
|
-
console.print(
|
|
487
|
-
f"[yellow]Warning: Prompt {prompt.name} is not in the prompts directory[/yellow]"
|
|
488
|
-
)
|
|
460
|
+
console.print(f"[yellow]Warning: Prompt {prompt.name} is not in the prompts directory[/yellow]")
|
|
489
461
|
continue
|
|
490
462
|
|
|
491
463
|
try:
|
|
@@ -520,16 +492,30 @@ class CodeGenerator:
|
|
|
520
492
|
config["endpoint_path"] = "" # No HTTP endpoint
|
|
521
493
|
else:
|
|
522
494
|
# Default to streamable-http
|
|
523
|
-
config["endpoint_path"] = "/mcp" # Default MCP path for FastMCP
|
|
495
|
+
config["endpoint_path"] = "/mcp/" # Default MCP path for FastMCP
|
|
524
496
|
|
|
525
497
|
return config
|
|
526
498
|
|
|
499
|
+
def _is_resource_template(self, component: ParsedComponent) -> bool:
|
|
500
|
+
"""Check if a resource component is a template (has URI parameters).
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
component: The parsed component to check
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
True if the resource has URI parameters, False otherwise
|
|
507
|
+
"""
|
|
508
|
+
return (
|
|
509
|
+
component.type == ComponentType.RESOURCE
|
|
510
|
+
and component.parameters is not None
|
|
511
|
+
and len(component.parameters) > 0
|
|
512
|
+
)
|
|
513
|
+
|
|
527
514
|
def _generate_server(self) -> None:
|
|
528
515
|
"""Generate the main server entry point."""
|
|
529
516
|
server_file = self.output_dir / "server.py"
|
|
530
517
|
|
|
531
518
|
# Get auth components
|
|
532
|
-
provider_config, _ = get_auth_config()
|
|
533
519
|
auth_components = generate_auth_code(
|
|
534
520
|
server_name=self.settings.name,
|
|
535
521
|
host=self.settings.host,
|
|
@@ -542,14 +528,20 @@ class CodeGenerator:
|
|
|
542
528
|
# Create imports section
|
|
543
529
|
imports = [
|
|
544
530
|
"from fastmcp import FastMCP",
|
|
531
|
+
"from fastmcp.tools import Tool",
|
|
532
|
+
"from fastmcp.resources import Resource, ResourceTemplate",
|
|
533
|
+
"from fastmcp.prompts import Prompt",
|
|
545
534
|
"import os",
|
|
546
535
|
"import sys",
|
|
547
536
|
"from dotenv import load_dotenv",
|
|
548
537
|
"import logging",
|
|
549
538
|
"",
|
|
550
539
|
"# Suppress FastMCP INFO logs",
|
|
551
|
-
"logging.getLogger('
|
|
552
|
-
"logging.getLogger('mcp').setLevel(logging.
|
|
540
|
+
"logging.getLogger('FastMCP').setLevel(logging.ERROR)",
|
|
541
|
+
"logging.getLogger('mcp').setLevel(logging.ERROR)",
|
|
542
|
+
"",
|
|
543
|
+
"# Golf utilities for MCP features (available for tool functions)",
|
|
544
|
+
"# from golf.utilities import elicit, sample, get_current_context",
|
|
553
545
|
"",
|
|
554
546
|
]
|
|
555
547
|
|
|
@@ -615,11 +607,7 @@ class CodeGenerator:
|
|
|
615
607
|
rel_to_tools = rel_path.relative_to(self.settings.tools_dir)
|
|
616
608
|
# Handle nested directories properly
|
|
617
609
|
if rel_to_tools.parent != Path("."):
|
|
618
|
-
parent_path = (
|
|
619
|
-
str(rel_to_tools.parent)
|
|
620
|
-
.replace("\\", ".")
|
|
621
|
-
.replace("/", ".")
|
|
622
|
-
)
|
|
610
|
+
parent_path = str(rel_to_tools.parent).replace("\\", ".").replace("/", ".")
|
|
623
611
|
import_path = f"components.tools.{parent_path}"
|
|
624
612
|
else:
|
|
625
613
|
import_path = "components.tools"
|
|
@@ -627,16 +615,10 @@ class CodeGenerator:
|
|
|
627
615
|
import_path = "components.tools"
|
|
628
616
|
elif component_type == ComponentType.RESOURCE:
|
|
629
617
|
try:
|
|
630
|
-
rel_to_resources = rel_path.relative_to(
|
|
631
|
-
self.settings.resources_dir
|
|
632
|
-
)
|
|
618
|
+
rel_to_resources = rel_path.relative_to(self.settings.resources_dir)
|
|
633
619
|
# Handle nested directories properly
|
|
634
620
|
if rel_to_resources.parent != Path("."):
|
|
635
|
-
parent_path = (
|
|
636
|
-
str(rel_to_resources.parent)
|
|
637
|
-
.replace("\\", ".")
|
|
638
|
-
.replace("/", ".")
|
|
639
|
-
)
|
|
621
|
+
parent_path = str(rel_to_resources.parent).replace("\\", ".").replace("/", ".")
|
|
640
622
|
import_path = f"components.resources.{parent_path}"
|
|
641
623
|
else:
|
|
642
624
|
import_path = "components.resources"
|
|
@@ -647,11 +629,7 @@ class CodeGenerator:
|
|
|
647
629
|
rel_to_prompts = rel_path.relative_to(self.settings.prompts_dir)
|
|
648
630
|
# Handle nested directories properly
|
|
649
631
|
if rel_to_prompts.parent != Path("."):
|
|
650
|
-
parent_path = (
|
|
651
|
-
str(rel_to_prompts.parent)
|
|
652
|
-
.replace("\\", ".")
|
|
653
|
-
.replace("/", ".")
|
|
654
|
-
)
|
|
632
|
+
parent_path = str(rel_to_prompts.parent).replace("\\", ".").replace("/", ".")
|
|
655
633
|
import_path = f"components.prompts.{parent_path}"
|
|
656
634
|
else:
|
|
657
635
|
import_path = "components.prompts"
|
|
@@ -668,14 +646,10 @@ class CodeGenerator:
|
|
|
668
646
|
# Add code to register this component
|
|
669
647
|
if self.settings.opentelemetry_enabled:
|
|
670
648
|
# Use telemetry instrumentation
|
|
671
|
-
registration =
|
|
672
|
-
f"# Register the {component_type.value} "
|
|
673
|
-
f"'{component.name}' with telemetry"
|
|
674
|
-
)
|
|
649
|
+
registration = f"# Register the {component_type.value} '{component.name}' with telemetry"
|
|
675
650
|
entry_func = (
|
|
676
651
|
component.entry_function
|
|
677
|
-
if hasattr(component, "entry_function")
|
|
678
|
-
and component.entry_function
|
|
652
|
+
if hasattr(component, "entry_function") and component.entry_function
|
|
679
653
|
else "export"
|
|
680
654
|
)
|
|
681
655
|
|
|
@@ -686,34 +660,42 @@ class CodeGenerator:
|
|
|
686
660
|
|
|
687
661
|
if component_type == ComponentType.TOOL:
|
|
688
662
|
registration += (
|
|
689
|
-
f
|
|
690
|
-
f'
|
|
663
|
+
f"\n_tool = Tool.from_function(_wrapped_func, "
|
|
664
|
+
f'name="{component.name}", '
|
|
665
|
+
f'description="{component.docstring or ""}")'
|
|
691
666
|
)
|
|
692
667
|
# Add annotations if present
|
|
693
668
|
if hasattr(component, "annotations") and component.annotations:
|
|
694
|
-
registration += f"
|
|
695
|
-
registration += ")"
|
|
669
|
+
registration += f".with_annotations({component.annotations})"
|
|
670
|
+
registration += "\nmcp.add_tool(_tool)"
|
|
696
671
|
elif component_type == ComponentType.RESOURCE:
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
672
|
+
if self._is_resource_template(component):
|
|
673
|
+
registration += (
|
|
674
|
+
f"\n_template = ResourceTemplate.from_function(_wrapped_func, "
|
|
675
|
+
f'uri_template="{component.uri_template}", name="{component.name}", '
|
|
676
|
+
f'description="{component.docstring or ""}")\n'
|
|
677
|
+
f"mcp.add_template(_template)"
|
|
678
|
+
)
|
|
679
|
+
else:
|
|
680
|
+
registration += (
|
|
681
|
+
f"\n_resource = Resource.from_function(_wrapped_func, "
|
|
682
|
+
f'uri="{component.uri_template}", name="{component.name}", '
|
|
683
|
+
f'description="{component.docstring or ""}")\n'
|
|
684
|
+
f"mcp.add_resource(_resource)"
|
|
685
|
+
)
|
|
702
686
|
else: # PROMPT
|
|
703
687
|
registration += (
|
|
704
|
-
f
|
|
705
|
-
f'
|
|
688
|
+
f"\n_prompt = Prompt.from_function(_wrapped_func, "
|
|
689
|
+
f'name="{component.name}", '
|
|
690
|
+
f'description="{component.docstring or ""}")\n'
|
|
691
|
+
f"mcp.add_prompt(_prompt)"
|
|
706
692
|
)
|
|
707
693
|
elif self.settings.metrics_enabled:
|
|
708
694
|
# Use metrics instrumentation
|
|
709
|
-
registration =
|
|
710
|
-
f"# Register the {component_type.value} "
|
|
711
|
-
f"'{component.name}' with metrics"
|
|
712
|
-
)
|
|
695
|
+
registration = f"# Register the {component_type.value} '{component.name}' with metrics"
|
|
713
696
|
entry_func = (
|
|
714
697
|
component.entry_function
|
|
715
|
-
if hasattr(component, "entry_function")
|
|
716
|
-
and component.entry_function
|
|
698
|
+
if hasattr(component, "entry_function") and component.entry_function
|
|
717
699
|
else "export"
|
|
718
700
|
)
|
|
719
701
|
|
|
@@ -724,37 +706,49 @@ class CodeGenerator:
|
|
|
724
706
|
|
|
725
707
|
if component_type == ComponentType.TOOL:
|
|
726
708
|
registration += (
|
|
727
|
-
f
|
|
728
|
-
f'
|
|
709
|
+
f"\n_tool = Tool.from_function(_wrapped_func, "
|
|
710
|
+
f'name="{component.name}", '
|
|
711
|
+
f'description="{component.docstring or ""}")'
|
|
729
712
|
)
|
|
730
713
|
# Add annotations if present
|
|
731
714
|
if hasattr(component, "annotations") and component.annotations:
|
|
732
|
-
registration += f"
|
|
733
|
-
registration += ")"
|
|
715
|
+
registration += f".with_annotations({component.annotations})"
|
|
716
|
+
registration += "\nmcp.add_tool(_tool)"
|
|
734
717
|
elif component_type == ComponentType.RESOURCE:
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
718
|
+
if self._is_resource_template(component):
|
|
719
|
+
registration += (
|
|
720
|
+
f"\n_template = ResourceTemplate.from_function(_wrapped_func, "
|
|
721
|
+
f'uri_template="{component.uri_template}", name="{component.name}", '
|
|
722
|
+
f'description="{component.docstring or ""}")\n'
|
|
723
|
+
f"mcp.add_template(_template)"
|
|
724
|
+
)
|
|
725
|
+
else:
|
|
726
|
+
registration += (
|
|
727
|
+
f"\n_resource = Resource.from_function(_wrapped_func, "
|
|
728
|
+
f'uri="{component.uri_template}", name="{component.name}", '
|
|
729
|
+
f'description="{component.docstring or ""}")\n'
|
|
730
|
+
f"mcp.add_resource(_resource)"
|
|
731
|
+
)
|
|
740
732
|
else: # PROMPT
|
|
741
733
|
registration += (
|
|
742
|
-
f
|
|
743
|
-
f'
|
|
734
|
+
f"\n_prompt = Prompt.from_function(_wrapped_func, "
|
|
735
|
+
f'name="{component.name}", '
|
|
736
|
+
f'description="{component.docstring or ""}")\n'
|
|
737
|
+
f"mcp.add_prompt(_prompt)"
|
|
744
738
|
)
|
|
745
739
|
else:
|
|
746
740
|
# Standard registration without telemetry
|
|
747
741
|
if component_type == ComponentType.TOOL:
|
|
748
742
|
registration = f"# Register the tool '{component.name}' from {full_module_path}"
|
|
749
743
|
|
|
750
|
-
# Use the entry_function if available, otherwise try the
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
744
|
+
# Use the entry_function if available, otherwise try the
|
|
745
|
+
# export variable
|
|
746
|
+
if hasattr(component, "entry_function") and component.entry_function:
|
|
747
|
+
registration += (
|
|
748
|
+
f"\n_tool = Tool.from_function({full_module_path}.{component.entry_function}"
|
|
749
|
+
)
|
|
756
750
|
else:
|
|
757
|
-
registration += f"\
|
|
751
|
+
registration += f"\n_tool = Tool.from_function({full_module_path}.export"
|
|
758
752
|
|
|
759
753
|
# Add the name parameter
|
|
760
754
|
registration += f', name="{component.name}"'
|
|
@@ -765,48 +759,85 @@ class CodeGenerator:
|
|
|
765
759
|
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
766
760
|
registration += f', description="{escaped_docstring}"'
|
|
767
761
|
|
|
762
|
+
registration += ")"
|
|
763
|
+
|
|
768
764
|
# Add annotations if present
|
|
769
765
|
if hasattr(component, "annotations") and component.annotations:
|
|
770
|
-
registration += f"
|
|
766
|
+
registration += f"\n_tool = _tool.with_annotations({component.annotations})"
|
|
771
767
|
|
|
772
|
-
registration += ")"
|
|
768
|
+
registration += "\nmcp.add_tool(_tool)"
|
|
773
769
|
|
|
774
770
|
elif component_type == ComponentType.RESOURCE:
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
hasattr(component, "entry_function")
|
|
780
|
-
and component.entry_function
|
|
781
|
-
):
|
|
782
|
-
registration += f'\nmcp.add_resource_fn({full_module_path}.{component.entry_function}, uri="{component.uri_template}"'
|
|
783
|
-
else:
|
|
784
|
-
registration += f'\nmcp.add_resource_fn({full_module_path}.export, uri="{component.uri_template}"'
|
|
785
|
-
|
|
786
|
-
# Add the name parameter
|
|
787
|
-
registration += f', name="{component.name}"'
|
|
788
|
-
|
|
789
|
-
# Add description from docstring
|
|
790
|
-
if component.docstring:
|
|
791
|
-
# Escape any quotes in the docstring
|
|
792
|
-
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
793
|
-
registration += f', description="{escaped_docstring}"'
|
|
771
|
+
if self._is_resource_template(component):
|
|
772
|
+
registration = (
|
|
773
|
+
f"# Register the resource template '{component.name}' from {full_module_path}"
|
|
774
|
+
)
|
|
794
775
|
|
|
795
|
-
|
|
776
|
+
# Use the entry_function if available, otherwise try the
|
|
777
|
+
# export variable
|
|
778
|
+
if hasattr(component, "entry_function") and component.entry_function:
|
|
779
|
+
registration += (
|
|
780
|
+
f"\n_template = ResourceTemplate.from_function("
|
|
781
|
+
f"{full_module_path}.{component.entry_function}, "
|
|
782
|
+
f'uri_template="{component.uri_template}"'
|
|
783
|
+
)
|
|
784
|
+
else:
|
|
785
|
+
registration += (
|
|
786
|
+
f"\n_template = ResourceTemplate.from_function("
|
|
787
|
+
f"{full_module_path}.export, "
|
|
788
|
+
f'uri_template="{component.uri_template}"'
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# Add the name parameter
|
|
792
|
+
registration += f', name="{component.name}"'
|
|
793
|
+
|
|
794
|
+
# Add description from docstring
|
|
795
|
+
if component.docstring:
|
|
796
|
+
# Escape any quotes in the docstring
|
|
797
|
+
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
798
|
+
registration += f', description="{escaped_docstring}"'
|
|
799
|
+
|
|
800
|
+
registration += ")\nmcp.add_template(_template)"
|
|
801
|
+
else:
|
|
802
|
+
registration = f"# Register the resource '{component.name}' from {full_module_path}"
|
|
803
|
+
|
|
804
|
+
# Use the entry_function if available, otherwise try the
|
|
805
|
+
# export variable
|
|
806
|
+
if hasattr(component, "entry_function") and component.entry_function:
|
|
807
|
+
registration += (
|
|
808
|
+
f"\n_resource = Resource.from_function("
|
|
809
|
+
f"{full_module_path}.{component.entry_function}, "
|
|
810
|
+
f'uri="{component.uri_template}"'
|
|
811
|
+
)
|
|
812
|
+
else:
|
|
813
|
+
registration += (
|
|
814
|
+
f"\n_resource = Resource.from_function("
|
|
815
|
+
f"{full_module_path}.export, "
|
|
816
|
+
f'uri="{component.uri_template}"'
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
# Add the name parameter
|
|
820
|
+
registration += f', name="{component.name}"'
|
|
821
|
+
|
|
822
|
+
# Add description from docstring
|
|
823
|
+
if component.docstring:
|
|
824
|
+
# Escape any quotes in the docstring
|
|
825
|
+
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
826
|
+
registration += f', description="{escaped_docstring}"'
|
|
827
|
+
|
|
828
|
+
registration += ")\nmcp.add_resource(_resource)"
|
|
796
829
|
|
|
797
830
|
else: # PROMPT
|
|
798
831
|
registration = f"# Register the prompt '{component.name}' from {full_module_path}"
|
|
799
832
|
|
|
800
|
-
# Use the entry_function if available, otherwise try the
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
and component.entry_function
|
|
804
|
-
):
|
|
805
|
-
registration += f"\nmcp.add_prompt({full_module_path}.{component.entry_function}"
|
|
806
|
-
else:
|
|
833
|
+
# Use the entry_function if available, otherwise try the
|
|
834
|
+
# export variable
|
|
835
|
+
if hasattr(component, "entry_function") and component.entry_function:
|
|
807
836
|
registration += (
|
|
808
|
-
f"\
|
|
837
|
+
f"\n_prompt = Prompt.from_function({full_module_path}.{component.entry_function}"
|
|
809
838
|
)
|
|
839
|
+
else:
|
|
840
|
+
registration += f"\n_prompt = Prompt.from_function({full_module_path}.export"
|
|
810
841
|
|
|
811
842
|
# Add the name parameter
|
|
812
843
|
registration += f', name="{component.name}"'
|
|
@@ -817,7 +848,7 @@ class CodeGenerator:
|
|
|
817
848
|
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
818
849
|
registration += f', description="{escaped_docstring}"'
|
|
819
850
|
|
|
820
|
-
registration += ")"
|
|
851
|
+
registration += ")\nmcp.add_prompt(_prompt)"
|
|
821
852
|
|
|
822
853
|
component_registrations.append(registration)
|
|
823
854
|
|
|
@@ -870,8 +901,9 @@ class CodeGenerator:
|
|
|
870
901
|
early_telemetry_init.extend(
|
|
871
902
|
[
|
|
872
903
|
"# Initialize telemetry early to ensure instrumentation works",
|
|
873
|
-
"from golf.telemetry.instrumentation import init_telemetry",
|
|
904
|
+
"from golf.telemetry.instrumentation import init_telemetry, set_detailed_tracing",
|
|
874
905
|
f'init_telemetry("{self.settings.name}")',
|
|
906
|
+
f"set_detailed_tracing({self.settings.detailed_tracing})",
|
|
875
907
|
"",
|
|
876
908
|
]
|
|
877
909
|
)
|
|
@@ -881,9 +913,7 @@ class CodeGenerator:
|
|
|
881
913
|
if self.settings.metrics_enabled:
|
|
882
914
|
from golf.core.builder_metrics import generate_metrics_initialization
|
|
883
915
|
|
|
884
|
-
early_metrics_init.extend(
|
|
885
|
-
generate_metrics_initialization(self.settings.name)
|
|
886
|
-
)
|
|
916
|
+
early_metrics_init.extend(generate_metrics_initialization(self.settings.name))
|
|
887
917
|
|
|
888
918
|
# Main entry point with transport-specific app initialization
|
|
889
919
|
main_code = [
|
|
@@ -892,22 +922,12 @@ class CodeGenerator:
|
|
|
892
922
|
" from rich.panel import Panel",
|
|
893
923
|
" console = Console()",
|
|
894
924
|
" # Get configuration from environment variables or use defaults",
|
|
895
|
-
' host = os.environ.get("HOST", "
|
|
925
|
+
' host = os.environ.get("HOST", "localhost")',
|
|
896
926
|
' port = int(os.environ.get("PORT", 3000))',
|
|
897
927
|
f' transport_to_run = "{self.settings.transport}"',
|
|
898
928
|
"",
|
|
899
929
|
]
|
|
900
930
|
|
|
901
|
-
# Add startup message
|
|
902
|
-
if self.settings.transport != "stdio":
|
|
903
|
-
main_code.append(
|
|
904
|
-
f' console.print(Panel.fit(f"[bold green]{{mcp.name}}[/bold green]\\n[dim]Running on http://{{host}}:{{port}}{endpoint_path} with transport \\"{{transport_to_run}}\\" (environment: {self.build_env})[/dim]", border_style="green"))'
|
|
905
|
-
)
|
|
906
|
-
else:
|
|
907
|
-
main_code.append(
|
|
908
|
-
f' console.print(Panel.fit(f"[bold green]{{mcp.name}}[/bold green]\\n[dim]Running with transport \\"{{transport_to_run}}\\" (environment: {self.build_env})[/dim]", border_style="green"))'
|
|
909
|
-
)
|
|
910
|
-
|
|
911
931
|
main_code.append("")
|
|
912
932
|
|
|
913
933
|
# Transport-specific run methods
|
|
@@ -918,26 +938,18 @@ class CodeGenerator:
|
|
|
918
938
|
|
|
919
939
|
api_key_config = get_api_key_config()
|
|
920
940
|
if auth_components.get("has_auth") and api_key_config:
|
|
921
|
-
middleware_setup.append(
|
|
922
|
-
" from starlette.middleware import Middleware"
|
|
923
|
-
)
|
|
941
|
+
middleware_setup.append(" from starlette.middleware import Middleware")
|
|
924
942
|
middleware_list.append("Middleware(ApiKeyMiddleware)")
|
|
925
943
|
|
|
926
944
|
# Add metrics middleware if enabled
|
|
927
945
|
if self.settings.metrics_enabled:
|
|
928
|
-
middleware_setup.append(
|
|
929
|
-
" from starlette.middleware import Middleware"
|
|
930
|
-
)
|
|
946
|
+
middleware_setup.append(" from starlette.middleware import Middleware")
|
|
931
947
|
middleware_list.append("Middleware(MetricsMiddleware)")
|
|
932
948
|
|
|
933
949
|
# Add OpenTelemetry middleware if enabled
|
|
934
950
|
if self.settings.opentelemetry_enabled:
|
|
935
|
-
middleware_setup.append(
|
|
936
|
-
|
|
937
|
-
)
|
|
938
|
-
middleware_setup.append(
|
|
939
|
-
" from starlette.middleware import Middleware"
|
|
940
|
-
)
|
|
951
|
+
middleware_setup.append(" from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware")
|
|
952
|
+
middleware_setup.append(" from starlette.middleware import Middleware")
|
|
941
953
|
middleware_list.append("Middleware(OpenTelemetryMiddleware)")
|
|
942
954
|
|
|
943
955
|
if middleware_setup:
|
|
@@ -947,14 +959,18 @@ class CodeGenerator:
|
|
|
947
959
|
main_code.extend(
|
|
948
960
|
[
|
|
949
961
|
" # Run SSE server with middleware using FastMCP's run method",
|
|
950
|
-
' mcp.run(transport="sse", host=host, port=port,
|
|
962
|
+
f' mcp.run(transport="sse", host=host, port=port, '
|
|
963
|
+
f'path="{endpoint_path}", log_level="info", '
|
|
964
|
+
f"middleware=middleware, show_banner=False)",
|
|
951
965
|
]
|
|
952
966
|
)
|
|
953
967
|
else:
|
|
954
968
|
main_code.extend(
|
|
955
969
|
[
|
|
956
970
|
" # Run SSE server using FastMCP's run method",
|
|
957
|
-
' mcp.run(transport="sse", host=host, port=port,
|
|
971
|
+
f' mcp.run(transport="sse", host=host, port=port, '
|
|
972
|
+
f'path="{endpoint_path}", log_level="info", '
|
|
973
|
+
f"show_banner=False)",
|
|
958
974
|
]
|
|
959
975
|
)
|
|
960
976
|
|
|
@@ -965,26 +981,18 @@ class CodeGenerator:
|
|
|
965
981
|
|
|
966
982
|
api_key_config = get_api_key_config()
|
|
967
983
|
if auth_components.get("has_auth") and api_key_config:
|
|
968
|
-
middleware_setup.append(
|
|
969
|
-
" from starlette.middleware import Middleware"
|
|
970
|
-
)
|
|
984
|
+
middleware_setup.append(" from starlette.middleware import Middleware")
|
|
971
985
|
middleware_list.append("Middleware(ApiKeyMiddleware)")
|
|
972
986
|
|
|
973
987
|
# Add metrics middleware if enabled
|
|
974
988
|
if self.settings.metrics_enabled:
|
|
975
|
-
middleware_setup.append(
|
|
976
|
-
" from starlette.middleware import Middleware"
|
|
977
|
-
)
|
|
989
|
+
middleware_setup.append(" from starlette.middleware import Middleware")
|
|
978
990
|
middleware_list.append("Middleware(MetricsMiddleware)")
|
|
979
991
|
|
|
980
992
|
# Add OpenTelemetry middleware if enabled
|
|
981
993
|
if self.settings.opentelemetry_enabled:
|
|
982
|
-
middleware_setup.append(
|
|
983
|
-
|
|
984
|
-
)
|
|
985
|
-
middleware_setup.append(
|
|
986
|
-
" from starlette.middleware import Middleware"
|
|
987
|
-
)
|
|
994
|
+
middleware_setup.append(" from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware")
|
|
995
|
+
middleware_setup.append(" from starlette.middleware import Middleware")
|
|
988
996
|
middleware_list.append("Middleware(OpenTelemetryMiddleware)")
|
|
989
997
|
|
|
990
998
|
if middleware_setup:
|
|
@@ -994,21 +1002,23 @@ class CodeGenerator:
|
|
|
994
1002
|
main_code.extend(
|
|
995
1003
|
[
|
|
996
1004
|
" # Run HTTP server with middleware using FastMCP's run method",
|
|
997
|
-
' mcp.run(transport="streamable-http", host=host,
|
|
1005
|
+
f' mcp.run(transport="streamable-http", host=host, '
|
|
1006
|
+
f'port=port, path="{endpoint_path}", log_level="info", '
|
|
1007
|
+
f"middleware=middleware, show_banner=False)",
|
|
998
1008
|
]
|
|
999
1009
|
)
|
|
1000
1010
|
else:
|
|
1001
1011
|
main_code.extend(
|
|
1002
1012
|
[
|
|
1003
1013
|
" # Run HTTP server using FastMCP's run method",
|
|
1004
|
-
' mcp.run(transport="streamable-http", host=host,
|
|
1014
|
+
f' mcp.run(transport="streamable-http", host=host, '
|
|
1015
|
+
f'port=port, path="{endpoint_path}", log_level="info", '
|
|
1016
|
+
f"show_banner=False)",
|
|
1005
1017
|
]
|
|
1006
1018
|
)
|
|
1007
1019
|
else:
|
|
1008
1020
|
# For stdio transport, use mcp.run()
|
|
1009
|
-
main_code.extend(
|
|
1010
|
-
[" # Run with stdio transport", ' mcp.run(transport="stdio")']
|
|
1011
|
-
)
|
|
1021
|
+
main_code.extend([" # Run with stdio transport", ' mcp.run(transport="stdio", show_banner=False)'])
|
|
1012
1022
|
|
|
1013
1023
|
# Add metrics route if enabled
|
|
1014
1024
|
metrics_route_code = []
|
|
@@ -1022,18 +1032,17 @@ class CodeGenerator:
|
|
|
1022
1032
|
if self.settings.health_check_enabled:
|
|
1023
1033
|
health_check_code = [
|
|
1024
1034
|
"# Add health check route",
|
|
1025
|
-
"@mcp.custom_route('"
|
|
1026
|
-
+ self.settings.health_check_path
|
|
1027
|
-
+ '\', methods=["GET"])',
|
|
1035
|
+
"@mcp.custom_route('" + self.settings.health_check_path + '\', methods=["GET"])',
|
|
1028
1036
|
"async def health_check(request: Request) -> PlainTextResponse:",
|
|
1029
1037
|
' """Health check endpoint for Kubernetes and load balancers."""',
|
|
1030
|
-
f' return PlainTextResponse("{self.settings.health_check_response}")',
|
|
1038
|
+
(f' return PlainTextResponse("{self.settings.health_check_response}")'),
|
|
1031
1039
|
"",
|
|
1032
1040
|
]
|
|
1033
1041
|
|
|
1034
1042
|
# Combine all sections
|
|
1035
1043
|
# Order: imports, env_section, auth_setup, server_code (mcp init),
|
|
1036
|
-
# early_telemetry_init, early_metrics_init, component_registrations,
|
|
1044
|
+
# early_telemetry_init, early_metrics_init, component_registrations,
|
|
1045
|
+
# metrics_route_code, health_check_code, main_code (run block)
|
|
1037
1046
|
code = "\n".join(
|
|
1038
1047
|
imports
|
|
1039
1048
|
+ env_section
|
|
@@ -1089,66 +1098,81 @@ def build_project(
|
|
|
1089
1098
|
if has_api_key and has_server_id:
|
|
1090
1099
|
console.print("[dim]Loaded Golf credentials for build operations[/dim]")
|
|
1091
1100
|
|
|
1092
|
-
# Execute
|
|
1093
|
-
|
|
1094
|
-
|
|
1101
|
+
# Execute auth.py if it exists (for authentication configuration)
|
|
1102
|
+
# Also support legacy pre_build.py for backward compatibility
|
|
1103
|
+
auth_path = project_path / "auth.py"
|
|
1104
|
+
legacy_path = project_path / "pre_build.py"
|
|
1105
|
+
|
|
1106
|
+
config_path = None
|
|
1107
|
+
if auth_path.exists():
|
|
1108
|
+
config_path = auth_path
|
|
1109
|
+
elif legacy_path.exists():
|
|
1110
|
+
config_path = legacy_path
|
|
1111
|
+
console.print("[yellow]Warning: pre_build.py is deprecated. Rename to auth.py[/yellow]")
|
|
1112
|
+
|
|
1113
|
+
if config_path:
|
|
1114
|
+
# Save the current directory and path - handle case where cwd might be invalid
|
|
1095
1115
|
try:
|
|
1096
|
-
# Save the current directory and path
|
|
1097
1116
|
original_dir = os.getcwd()
|
|
1098
|
-
|
|
1117
|
+
except (FileNotFoundError, OSError):
|
|
1118
|
+
# Current directory might have been deleted by previous operations,
|
|
1119
|
+
# use project_path as fallback
|
|
1120
|
+
original_dir = str(project_path)
|
|
1121
|
+
os.chdir(original_dir)
|
|
1122
|
+
original_path = sys.path.copy()
|
|
1099
1123
|
|
|
1124
|
+
try:
|
|
1100
1125
|
# Change to the project directory and add it to Python path
|
|
1101
1126
|
os.chdir(project_path)
|
|
1102
1127
|
sys.path.insert(0, str(project_path))
|
|
1103
1128
|
|
|
1104
|
-
# Execute the
|
|
1105
|
-
with open(
|
|
1129
|
+
# Execute the auth configuration script
|
|
1130
|
+
with open(config_path) as f:
|
|
1106
1131
|
script_content = f.read()
|
|
1107
1132
|
|
|
1108
1133
|
# Print the first few lines for debugging
|
|
1109
1134
|
"\n".join(script_content.split("\n")[:5]) + "\n..."
|
|
1110
1135
|
|
|
1111
1136
|
# Use exec to run the script as a module
|
|
1112
|
-
code = compile(script_content, str(
|
|
1137
|
+
code = compile(script_content, str(config_path), "exec")
|
|
1113
1138
|
exec(code, {})
|
|
1114
1139
|
|
|
1115
|
-
# Check if auth was configured by the script
|
|
1116
|
-
provider, scopes = get_auth_config()
|
|
1117
|
-
|
|
1118
|
-
# Restore original directory and path
|
|
1119
|
-
os.chdir(original_dir)
|
|
1120
|
-
sys.path = original_path
|
|
1121
|
-
|
|
1122
1140
|
except Exception as e:
|
|
1123
|
-
console.print(f"[red]Error executing
|
|
1141
|
+
console.print(f"[red]Error executing {config_path.name}: {str(e)}[/red]")
|
|
1124
1142
|
import traceback
|
|
1125
1143
|
|
|
1126
1144
|
console.print(f"[red]{traceback.format_exc()}[/red]")
|
|
1127
1145
|
|
|
1128
|
-
# Track detailed error for
|
|
1146
|
+
# Track detailed error for auth.py execution failures
|
|
1129
1147
|
try:
|
|
1130
1148
|
from golf.core.telemetry import track_detailed_error
|
|
1131
1149
|
|
|
1132
1150
|
track_detailed_error(
|
|
1133
|
-
"
|
|
1151
|
+
"build_auth_failed",
|
|
1134
1152
|
e,
|
|
1135
|
-
context="Executing
|
|
1136
|
-
operation="
|
|
1153
|
+
context=f"Executing {config_path.name} configuration script",
|
|
1154
|
+
operation="auth_execution",
|
|
1137
1155
|
additional_props={
|
|
1138
|
-
"file_path": str(
|
|
1156
|
+
"file_path": str(config_path.relative_to(project_path)),
|
|
1139
1157
|
"build_env": build_env,
|
|
1140
1158
|
},
|
|
1141
1159
|
)
|
|
1142
1160
|
except Exception:
|
|
1143
1161
|
# Don't let telemetry errors break the build
|
|
1144
1162
|
pass
|
|
1163
|
+
finally:
|
|
1164
|
+
# Always restore original directory and path, even if an exception occurred
|
|
1165
|
+
try:
|
|
1166
|
+
os.chdir(original_dir)
|
|
1167
|
+
sys.path = original_path
|
|
1168
|
+
except Exception:
|
|
1169
|
+
# If we can't restore the directory, at least try to reset the path
|
|
1170
|
+
sys.path = original_path
|
|
1145
1171
|
|
|
1146
1172
|
# Clear the output directory if it exists
|
|
1147
1173
|
if output_dir.exists():
|
|
1148
1174
|
shutil.rmtree(output_dir)
|
|
1149
|
-
output_dir.mkdir(
|
|
1150
|
-
parents=True, exist_ok=True
|
|
1151
|
-
) # Ensure output_dir exists after clearing
|
|
1175
|
+
output_dir.mkdir(parents=True, exist_ok=True) # Ensure output_dir exists after clearing
|
|
1152
1176
|
|
|
1153
1177
|
# --- BEGIN Enhanced .env handling ---
|
|
1154
1178
|
env_vars_to_write = {}
|
|
@@ -1164,38 +1188,36 @@ def build_project(
|
|
|
1164
1188
|
env_vars_to_write.update(dotenv_values(project_env_file))
|
|
1165
1189
|
except ImportError:
|
|
1166
1190
|
console.print(
|
|
1167
|
-
"[yellow]Warning: python-dotenv is not installed.
|
|
1191
|
+
"[yellow]Warning: python-dotenv is not installed. "
|
|
1192
|
+
"Cannot read existing .env file for rich merging. "
|
|
1193
|
+
"Copying directly.[/yellow]"
|
|
1168
1194
|
)
|
|
1169
1195
|
try:
|
|
1170
1196
|
shutil.copy(project_env_file, env_file_path)
|
|
1171
|
-
# If direct copy happens, re-read for step 2 & 3 to respect
|
|
1197
|
+
# If direct copy happens, re-read for step 2 & 3 to respect
|
|
1198
|
+
# its content
|
|
1172
1199
|
if env_file_path.exists():
|
|
1173
1200
|
from dotenv import dotenv_values
|
|
1174
1201
|
|
|
1175
|
-
env_vars_to_write.update(
|
|
1176
|
-
dotenv_values(env_file_path)
|
|
1177
|
-
) # Read what was copied
|
|
1202
|
+
env_vars_to_write.update(dotenv_values(env_file_path)) # Read what was copied
|
|
1178
1203
|
except Exception as e:
|
|
1179
|
-
console.print(
|
|
1180
|
-
f"[yellow]Warning: Could not copy project .env file: {e}[/yellow]"
|
|
1181
|
-
)
|
|
1204
|
+
console.print(f"[yellow]Warning: Could not copy project .env file: {e}[/yellow]")
|
|
1182
1205
|
except Exception as e:
|
|
1183
|
-
console.print(
|
|
1184
|
-
f"[yellow]Warning: Error reading project .env file content: {e}[/yellow]"
|
|
1185
|
-
)
|
|
1206
|
+
console.print(f"[yellow]Warning: Error reading project .env file content: {e}[/yellow]")
|
|
1186
1207
|
|
|
1187
|
-
# 2. Apply Golf's OTel default exporter setting if OTEL_TRACES_EXPORTER
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1208
|
+
# 2. Apply Golf's OTel default exporter setting if OTEL_TRACES_EXPORTER
|
|
1209
|
+
# is not already set
|
|
1210
|
+
if (
|
|
1211
|
+
settings.opentelemetry_enabled
|
|
1212
|
+
and settings.opentelemetry_default_exporter
|
|
1213
|
+
and "OTEL_TRACES_EXPORTER" not in env_vars_to_write
|
|
1214
|
+
):
|
|
1215
|
+
env_vars_to_write["OTEL_TRACES_EXPORTER"] = settings.opentelemetry_default_exporter
|
|
1193
1216
|
|
|
1194
1217
|
# 3. Apply Golf's project name as OTEL_SERVICE_NAME if not already set
|
|
1195
1218
|
# (Ensures service name defaults to project name if not specified in user's .env)
|
|
1196
|
-
if settings.opentelemetry_enabled and settings.name:
|
|
1197
|
-
|
|
1198
|
-
env_vars_to_write["OTEL_SERVICE_NAME"] = settings.name
|
|
1219
|
+
if settings.opentelemetry_enabled and settings.name and "OTEL_SERVICE_NAME" not in env_vars_to_write:
|
|
1220
|
+
env_vars_to_write["OTEL_SERVICE_NAME"] = settings.name
|
|
1199
1221
|
|
|
1200
1222
|
# 4. (Re-)Write the .env file in the output directory if there's anything to write
|
|
1201
1223
|
if env_vars_to_write:
|
|
@@ -1206,73 +1228,51 @@ def build_project(
|
|
|
1206
1228
|
# and handle existing quotes within the value.
|
|
1207
1229
|
if isinstance(value, str):
|
|
1208
1230
|
# Replace backslashes first, then double quotes
|
|
1209
|
-
processed_value = value.replace(
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
processed_value = processed_value.replace(
|
|
1213
|
-
'"', '\\"'
|
|
1214
|
-
) # Escape double quotes
|
|
1215
|
-
if (
|
|
1216
|
-
" " in value
|
|
1217
|
-
or "#" in value
|
|
1218
|
-
or "\n" in value
|
|
1219
|
-
or '"' in value
|
|
1220
|
-
or "'" in value
|
|
1221
|
-
):
|
|
1231
|
+
processed_value = value.replace("\\", "\\\\") # Escape backslashes
|
|
1232
|
+
processed_value = processed_value.replace('"', '\\"') # Escape double quotes
|
|
1233
|
+
if " " in value or "#" in value or "\n" in value or '"' in value or "'" in value:
|
|
1222
1234
|
f.write(f'{key}="{processed_value}"\n')
|
|
1223
1235
|
else:
|
|
1224
1236
|
f.write(f"{key}={processed_value}\n")
|
|
1225
1237
|
else: # For non-string values, write directly
|
|
1226
1238
|
f.write(f"{key}={value}\n")
|
|
1227
1239
|
except Exception as e:
|
|
1228
|
-
console.print(
|
|
1229
|
-
f"[yellow]Warning: Could not write .env file to output directory: {e}[/yellow]"
|
|
1230
|
-
)
|
|
1240
|
+
console.print(f"[yellow]Warning: Could not write .env file to output directory: {e}[/yellow]")
|
|
1231
1241
|
# --- END Enhanced .env handling ---
|
|
1232
1242
|
|
|
1233
1243
|
# Show what we're building, with environment info
|
|
1234
|
-
console
|
|
1235
|
-
f"[bold]Building [green]{settings.name}[/green] ({build_env} environment)[/bold]"
|
|
1236
|
-
)
|
|
1244
|
+
create_build_header(settings.name, build_env, console)
|
|
1237
1245
|
|
|
1238
1246
|
# Generate the code
|
|
1239
|
-
generator = CodeGenerator(
|
|
1240
|
-
project_path, settings, output_dir, build_env=build_env, copy_env=copy_env
|
|
1241
|
-
)
|
|
1247
|
+
generator = CodeGenerator(project_path, settings, output_dir, build_env=build_env, copy_env=copy_env)
|
|
1242
1248
|
generator.generate()
|
|
1243
1249
|
|
|
1244
1250
|
# Platform registration (only for prod builds)
|
|
1245
1251
|
if build_env == "prod":
|
|
1246
|
-
console.print(
|
|
1247
|
-
|
|
1248
|
-
)
|
|
1249
|
-
|
|
1252
|
+
console.print()
|
|
1253
|
+
status_msg = f"[{GOLF_BLUE}]{STATUS_ICONS['platform']} Registering with Golf platform and updating resources...[/{GOLF_BLUE}]"
|
|
1254
|
+
with console.status(status_msg):
|
|
1255
|
+
import asyncio
|
|
1250
1256
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1257
|
+
try:
|
|
1258
|
+
from golf.core.platform import register_project_with_platform
|
|
1253
1259
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1260
|
+
success = asyncio.run(
|
|
1261
|
+
register_project_with_platform(
|
|
1262
|
+
project_path=project_path,
|
|
1263
|
+
settings=settings,
|
|
1264
|
+
components=generator.components,
|
|
1265
|
+
)
|
|
1259
1266
|
)
|
|
1260
|
-
)
|
|
1261
1267
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
console.print(
|
|
1271
|
-
f"[yellow]Warning: Platform registration failed: {e}[/yellow]"
|
|
1272
|
-
)
|
|
1273
|
-
console.print(
|
|
1274
|
-
"[yellow]Tip: Ensure GOLF_API_KEY and GOLF_SERVER_ID are available in your .env file[/yellow]"
|
|
1275
|
-
)
|
|
1268
|
+
if success:
|
|
1269
|
+
console.print(get_status_text("success", "Platform registration completed"))
|
|
1270
|
+
# If success is False, the platform module already printed appropriate warnings
|
|
1271
|
+
except ImportError:
|
|
1272
|
+
console.print(get_status_text("warning", "Platform registration module not available"))
|
|
1273
|
+
except Exception as e:
|
|
1274
|
+
console.print(get_status_text("warning", f"Platform registration failed: {e}"))
|
|
1275
|
+
console.print("[dim]Tip: Ensure GOLF_API_KEY and GOLF_SERVER_ID are available in your .env file[/dim]")
|
|
1276
1276
|
|
|
1277
1277
|
# Create a simple README
|
|
1278
1278
|
readme_content = f"""# {settings.name}
|
|
@@ -1292,50 +1292,6 @@ This is a standalone FastMCP server generated by GolfMCP.
|
|
|
1292
1292
|
with open(output_dir / "README.md", "w") as f:
|
|
1293
1293
|
f.write(readme_content)
|
|
1294
1294
|
|
|
1295
|
-
# Copy pyproject.toml with required dependencies
|
|
1296
|
-
base_dependencies = [
|
|
1297
|
-
"fastmcp>=2.0.0,<2.6.0",
|
|
1298
|
-
"uvicorn>=0.20.0",
|
|
1299
|
-
"pydantic>=2.0.0",
|
|
1300
|
-
"python-dotenv>=1.0.0",
|
|
1301
|
-
]
|
|
1302
|
-
|
|
1303
|
-
# Add OpenTelemetry dependencies if enabled
|
|
1304
|
-
if settings.opentelemetry_enabled:
|
|
1305
|
-
base_dependencies.extend(get_otel_dependencies())
|
|
1306
|
-
|
|
1307
|
-
# Add authentication dependencies if enabled, before generating pyproject_content
|
|
1308
|
-
provider_config, required_scopes = (
|
|
1309
|
-
get_auth_config()
|
|
1310
|
-
) # Ensure this is called to check for auth
|
|
1311
|
-
if provider_config:
|
|
1312
|
-
base_dependencies.extend(
|
|
1313
|
-
[
|
|
1314
|
-
"pyjwt>=2.0.0",
|
|
1315
|
-
"httpx>=0.20.0",
|
|
1316
|
-
]
|
|
1317
|
-
)
|
|
1318
|
-
|
|
1319
|
-
# Create the dependencies string
|
|
1320
|
-
dependencies_str = ",\n ".join([f'"{dep}"' for dep in base_dependencies])
|
|
1321
|
-
|
|
1322
|
-
pyproject_content = f"""[build-system]
|
|
1323
|
-
requires = ["setuptools>=61.0"]
|
|
1324
|
-
build-backend = "setuptools.build_meta"
|
|
1325
|
-
|
|
1326
|
-
[project]
|
|
1327
|
-
name = "generated-fastmcp-app"
|
|
1328
|
-
version = "0.1.0"
|
|
1329
|
-
description = "Generated FastMCP Application"
|
|
1330
|
-
requires-python = ">=3.10"
|
|
1331
|
-
dependencies = [
|
|
1332
|
-
{dependencies_str}
|
|
1333
|
-
]
|
|
1334
|
-
"""
|
|
1335
|
-
|
|
1336
|
-
with open(output_dir / "pyproject.toml", "w") as f:
|
|
1337
|
-
f.write(pyproject_content)
|
|
1338
|
-
|
|
1339
1295
|
# Always copy the auth module so it's available
|
|
1340
1296
|
auth_dir = output_dir / "golf" / "auth"
|
|
1341
1297
|
auth_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -1345,24 +1301,24 @@ dependencies = [
|
|
|
1345
1301
|
f.write(
|
|
1346
1302
|
"""\"\"\"Auth module for GolfMCP.\"\"\"
|
|
1347
1303
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
from golf.auth.helpers import
|
|
1304
|
+
# Legacy ProviderConfig removed in Golf 0.2.x - use modern auth configurations
|
|
1305
|
+
# Legacy OAuth imports removed in Golf 0.2.x - use FastMCP 2.11+ auth providers
|
|
1306
|
+
from golf.auth.helpers import get_provider_token, extract_token_from_header, get_api_key, set_api_key
|
|
1351
1307
|
from golf.auth.api_key import configure_api_key, get_api_key_config
|
|
1308
|
+
from golf.auth.factory import create_auth_provider
|
|
1309
|
+
from golf.auth.providers import RemoteAuthConfig, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig
|
|
1352
1310
|
"""
|
|
1353
1311
|
)
|
|
1354
1312
|
|
|
1355
|
-
# Copy
|
|
1356
|
-
for module in ["
|
|
1313
|
+
# Copy auth modules required for Golf 0.2.x
|
|
1314
|
+
for module in ["helpers.py", "api_key.py", "factory.py", "providers.py"]:
|
|
1357
1315
|
src_file = Path(__file__).parent.parent.parent / "golf" / "auth" / module
|
|
1358
1316
|
dst_file = auth_dir / module
|
|
1359
1317
|
|
|
1360
1318
|
if src_file.exists():
|
|
1361
1319
|
shutil.copy(src_file, dst_file)
|
|
1362
1320
|
else:
|
|
1363
|
-
console.print(
|
|
1364
|
-
f"[yellow]Warning: Could not find {src_file} to copy[/yellow]"
|
|
1365
|
-
)
|
|
1321
|
+
console.print(f"[yellow]Warning: Could not find {src_file} to copy[/yellow]")
|
|
1366
1322
|
|
|
1367
1323
|
# Copy telemetry module if OpenTelemetry is enabled
|
|
1368
1324
|
if settings.opentelemetry_enabled:
|
|
@@ -1370,31 +1326,21 @@ from golf.auth.api_key import configure_api_key, get_api_key_config
|
|
|
1370
1326
|
telemetry_dir.mkdir(parents=True, exist_ok=True)
|
|
1371
1327
|
|
|
1372
1328
|
# Copy telemetry __init__.py
|
|
1373
|
-
src_init = (
|
|
1374
|
-
Path(__file__).parent.parent.parent / "golf" / "telemetry" / "__init__.py"
|
|
1375
|
-
)
|
|
1329
|
+
src_init = Path(__file__).parent.parent.parent / "golf" / "telemetry" / "__init__.py"
|
|
1376
1330
|
dst_init = telemetry_dir / "__init__.py"
|
|
1377
1331
|
if src_init.exists():
|
|
1378
1332
|
shutil.copy(src_init, dst_init)
|
|
1379
1333
|
|
|
1380
1334
|
# Copy instrumentation module
|
|
1381
|
-
src_instrumentation = (
|
|
1382
|
-
Path(__file__).parent.parent.parent
|
|
1383
|
-
/ "golf"
|
|
1384
|
-
/ "telemetry"
|
|
1385
|
-
/ "instrumentation.py"
|
|
1386
|
-
)
|
|
1335
|
+
src_instrumentation = Path(__file__).parent.parent.parent / "golf" / "telemetry" / "instrumentation.py"
|
|
1387
1336
|
dst_instrumentation = telemetry_dir / "instrumentation.py"
|
|
1388
1337
|
if src_instrumentation.exists():
|
|
1389
1338
|
shutil.copy(src_instrumentation, dst_instrumentation)
|
|
1390
1339
|
else:
|
|
1391
|
-
console.print(
|
|
1392
|
-
"[yellow]Warning: Could not find telemetry instrumentation module[/yellow]"
|
|
1393
|
-
)
|
|
1340
|
+
console.print("[yellow]Warning: Could not find telemetry instrumentation module[/yellow]")
|
|
1394
1341
|
|
|
1395
1342
|
# Check if auth routes need to be added
|
|
1396
|
-
|
|
1397
|
-
if provider_config:
|
|
1343
|
+
if is_auth_configured() or get_api_key_config():
|
|
1398
1344
|
auth_routes_code = generate_auth_routes()
|
|
1399
1345
|
|
|
1400
1346
|
server_file = output_dir / "server.py"
|
|
@@ -1407,17 +1353,12 @@ from golf.auth.api_key import configure_api_key, get_api_key_config
|
|
|
1407
1353
|
app_pos = server_code_content.find(app_marker)
|
|
1408
1354
|
if app_pos != -1:
|
|
1409
1355
|
modified_code = (
|
|
1410
|
-
server_code_content[:app_pos]
|
|
1411
|
-
+ auth_routes_code
|
|
1412
|
-
+ "\n\n"
|
|
1413
|
-
+ server_code_content[app_pos:]
|
|
1356
|
+
server_code_content[:app_pos] + auth_routes_code + "\n\n" + server_code_content[app_pos:]
|
|
1414
1357
|
)
|
|
1415
1358
|
|
|
1416
1359
|
# Format with black before writing
|
|
1417
1360
|
try:
|
|
1418
|
-
final_code_to_write = black.format_str(
|
|
1419
|
-
modified_code, mode=black.Mode()
|
|
1420
|
-
)
|
|
1361
|
+
final_code_to_write = black.format_str(modified_code, mode=black.Mode())
|
|
1421
1362
|
except Exception as e:
|
|
1422
1363
|
console.print(
|
|
1423
1364
|
f"[yellow]Warning: Could not format server.py after auth routes injection: {e}[/yellow]"
|
|
@@ -1432,38 +1373,29 @@ from golf.auth.api_key import configure_api_key, get_api_key_config
|
|
|
1432
1373
|
)
|
|
1433
1374
|
|
|
1434
1375
|
|
|
1435
|
-
#
|
|
1436
|
-
def find_common_files(
|
|
1437
|
-
project_path: Path, components: dict[ComponentType, list[ParsedComponent]]
|
|
1438
|
-
) -> dict[str, Path]:
|
|
1439
|
-
"""Find all common.py files used by components."""
|
|
1440
|
-
# We'll use the parser's functionality to find common files directly
|
|
1441
|
-
from golf.core.parser import parse_common_files
|
|
1442
|
-
|
|
1443
|
-
common_files = parse_common_files(project_path)
|
|
1444
|
-
|
|
1445
|
-
# Return the found files without debug messages
|
|
1446
|
-
return common_files
|
|
1376
|
+
# Legacy function removed - replaced by parse_shared_files in parser module
|
|
1447
1377
|
|
|
1448
1378
|
|
|
1449
|
-
# Updated
|
|
1450
|
-
def build_import_map(
|
|
1451
|
-
project_path: Path, common_files: dict[str, Path]
|
|
1452
|
-
) -> dict[str, str]:
|
|
1379
|
+
# Updated to handle any shared file, not just common.py files
|
|
1380
|
+
def build_import_map(project_path: Path, shared_files: dict[str, Path]) -> dict[str, str]:
|
|
1453
1381
|
"""Build a mapping of import paths to their new locations in the build output.
|
|
1454
1382
|
|
|
1455
1383
|
This maps from original relative import paths to absolute import paths
|
|
1456
1384
|
in the components directory structure.
|
|
1385
|
+
|
|
1386
|
+
Args:
|
|
1387
|
+
project_path: Path to the project root
|
|
1388
|
+
shared_files: Dictionary mapping module paths to shared file paths
|
|
1457
1389
|
"""
|
|
1458
1390
|
import_map = {}
|
|
1459
1391
|
|
|
1460
|
-
for
|
|
1461
|
-
# Convert
|
|
1462
|
-
|
|
1392
|
+
for module_path_str, file_path in shared_files.items():
|
|
1393
|
+
# Convert module path to Path object (e.g., "tools/weather/helpers" -> Path("tools/weather/helpers"))
|
|
1394
|
+
module_path = Path(module_path_str)
|
|
1463
1395
|
|
|
1464
1396
|
# Get the component type (tools, resources, prompts)
|
|
1465
1397
|
component_type = None
|
|
1466
|
-
for part in
|
|
1398
|
+
for part in module_path.parts:
|
|
1467
1399
|
if part in ["tools", "resources", "prompts"]:
|
|
1468
1400
|
component_type = part
|
|
1469
1401
|
break
|
|
@@ -1473,23 +1405,32 @@ def build_import_map(
|
|
|
1473
1405
|
|
|
1474
1406
|
# Calculate the relative path within the component type
|
|
1475
1407
|
try:
|
|
1476
|
-
rel_to_component =
|
|
1408
|
+
rel_to_component = module_path.relative_to(component_type)
|
|
1477
1409
|
# Create the new import path
|
|
1478
1410
|
if str(rel_to_component) == ".":
|
|
1479
|
-
# This
|
|
1411
|
+
# This shouldn't happen for individual files, but handle it
|
|
1480
1412
|
new_path = f"components.{component_type}"
|
|
1481
1413
|
else:
|
|
1482
1414
|
# Replace path separators with dots
|
|
1483
1415
|
path_parts = str(rel_to_component).replace("\\", "/").split("/")
|
|
1484
1416
|
new_path = f"components.{component_type}.{'.'.join(path_parts)}"
|
|
1485
1417
|
|
|
1486
|
-
# Map
|
|
1487
|
-
|
|
1488
|
-
import_map[
|
|
1418
|
+
# Map the specific shared module
|
|
1419
|
+
# e.g., "tools/weather/helpers" -> "components.tools.weather.helpers"
|
|
1420
|
+
import_map[module_path_str] = new_path
|
|
1421
|
+
|
|
1422
|
+
# Also map the directory path for relative imports
|
|
1423
|
+
# e.g., "tools/weather" -> "components.tools.weather"
|
|
1424
|
+
dir_path_str = str(module_path.parent)
|
|
1425
|
+
if dir_path_str != "." and dir_path_str not in import_map:
|
|
1426
|
+
dir_rel_to_component = module_path.parent.relative_to(component_type)
|
|
1427
|
+
if str(dir_rel_to_component) == ".":
|
|
1428
|
+
dir_new_path = f"components.{component_type}"
|
|
1429
|
+
else:
|
|
1430
|
+
dir_path_parts = str(dir_rel_to_component).replace("\\", "/").split("/")
|
|
1431
|
+
dir_new_path = f"components.{component_type}.{'.'.join(dir_path_parts)}"
|
|
1432
|
+
import_map[dir_path_str] = dir_new_path
|
|
1489
1433
|
|
|
1490
|
-
# Also map the specific common module
|
|
1491
|
-
common_module = f"{dir_path_str}/common"
|
|
1492
|
-
import_map[common_module] = f"{new_path}.common"
|
|
1493
1434
|
except ValueError:
|
|
1494
1435
|
continue
|
|
1495
1436
|
|