golf-mcp 0.2.13__tar.gz → 0.2.15__tar.gz
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_mcp-0.2.13/src/golf_mcp.egg-info → golf_mcp-0.2.15}/PKG-INFO +1 -1
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/pyproject.toml +2 -2
- golf_mcp-0.2.15/src/golf/__init__.py +1 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder.py +32 -19
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/transformer.py +65 -7
- {golf_mcp-0.2.13 → golf_mcp-0.2.15/src/golf_mcp.egg-info}/PKG-INFO +1 -1
- golf_mcp-0.2.13/src/golf/__init__.py +0 -1
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/LICENSE +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/MANIFEST.in +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/README.md +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/setup.cfg +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/api_key.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/factory.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/helpers.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/providers.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/registry.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/cli/branding.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/cli/main.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/init.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/run.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder_auth.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder_metrics.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/config.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/parser.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/telemetry.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/.env.example +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/auth.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/golf.json +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/city.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/client.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/tools/calculator.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/tools/say/hello.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/metrics/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/metrics/collector.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/metrics/registry.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/telemetry/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/telemetry/instrumentation.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/__init__.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/context.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/elicitation.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/sampling.py +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/SOURCES.txt +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/requires.txt +0 -0
- {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "golf-mcp"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.15"
|
|
8
8
|
description = "Framework for building MCP servers"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Antoni Gmitruk", email = "antoni@golf.dev"}
|
|
@@ -66,7 +66,7 @@ golf = ["examples/**/*"]
|
|
|
66
66
|
|
|
67
67
|
[tool.poetry]
|
|
68
68
|
name = "golf-mcp"
|
|
69
|
-
version = "0.2.
|
|
69
|
+
version = "0.2.14"
|
|
70
70
|
description = "Framework for building MCP servers with zero boilerplate"
|
|
71
71
|
authors = ["Antoni Gmitruk <antoni@golf.dev>"]
|
|
72
72
|
license = "Apache-2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.15"
|
|
@@ -341,6 +341,13 @@ class CodeGenerator:
|
|
|
341
341
|
self.manifest = {}
|
|
342
342
|
self.shared_files = {}
|
|
343
343
|
self.import_map = {}
|
|
344
|
+
self._root_files_cache = None # Cache for discovered root files
|
|
345
|
+
|
|
346
|
+
def _get_cached_root_files(self) -> dict[str, Path]:
|
|
347
|
+
"""Get cached root files, discovering them only once."""
|
|
348
|
+
if self._root_files_cache is None:
|
|
349
|
+
self._root_files_cache = discover_root_files(self.project_path)
|
|
350
|
+
return self._root_files_cache
|
|
344
351
|
|
|
345
352
|
def generate(self) -> None:
|
|
346
353
|
"""Generate the FastMCP application code."""
|
|
@@ -386,7 +393,7 @@ class CodeGenerator:
|
|
|
386
393
|
def _generate_root_file_imports(self) -> list[str]:
|
|
387
394
|
"""Generate import statements for automatically discovered root files."""
|
|
388
395
|
root_file_imports = []
|
|
389
|
-
discovered_files =
|
|
396
|
+
discovered_files = self._get_cached_root_files()
|
|
390
397
|
|
|
391
398
|
if discovered_files:
|
|
392
399
|
root_file_imports.append("# Import root-level Python files")
|
|
@@ -399,6 +406,11 @@ class CodeGenerator:
|
|
|
399
406
|
|
|
400
407
|
return root_file_imports
|
|
401
408
|
|
|
409
|
+
def _get_root_file_modules(self) -> set[str]:
|
|
410
|
+
"""Get set of root file module names for import transformation."""
|
|
411
|
+
discovered_files = self._get_cached_root_files()
|
|
412
|
+
return {Path(filename).stem for filename in discovered_files.keys()}
|
|
413
|
+
|
|
402
414
|
def _create_directory_structure(self) -> None:
|
|
403
415
|
"""Create the output directory structure"""
|
|
404
416
|
# Create main directories
|
|
@@ -444,12 +456,14 @@ class CodeGenerator:
|
|
|
444
456
|
target_file = target_dir / shared_file.name
|
|
445
457
|
|
|
446
458
|
# Use transformer to process the file
|
|
459
|
+
root_file_modules = self._get_root_file_modules()
|
|
447
460
|
transform_component(
|
|
448
461
|
component=None,
|
|
449
462
|
output_file=target_file,
|
|
450
463
|
project_path=self.project_path,
|
|
451
464
|
import_map=self.import_map,
|
|
452
465
|
source_file=shared_file,
|
|
466
|
+
root_file_modules=root_file_modules,
|
|
453
467
|
)
|
|
454
468
|
|
|
455
469
|
def _generate_tools(self) -> None:
|
|
@@ -474,7 +488,10 @@ class CodeGenerator:
|
|
|
474
488
|
|
|
475
489
|
# Create the tool file
|
|
476
490
|
output_file = tool_dir / rel_path.name
|
|
477
|
-
|
|
491
|
+
root_file_modules = self._get_root_file_modules()
|
|
492
|
+
transform_component(
|
|
493
|
+
tool, output_file, self.project_path, self.import_map, root_file_modules=root_file_modules
|
|
494
|
+
)
|
|
478
495
|
|
|
479
496
|
def _generate_resources(self) -> None:
|
|
480
497
|
"""Generate code for all resources."""
|
|
@@ -498,7 +515,10 @@ class CodeGenerator:
|
|
|
498
515
|
|
|
499
516
|
# Create the resource file
|
|
500
517
|
output_file = resource_dir / rel_path.name
|
|
501
|
-
|
|
518
|
+
root_file_modules = self._get_root_file_modules()
|
|
519
|
+
transform_component(
|
|
520
|
+
resource, output_file, self.project_path, self.import_map, root_file_modules=root_file_modules
|
|
521
|
+
)
|
|
502
522
|
|
|
503
523
|
def _generate_prompts(self) -> None:
|
|
504
524
|
"""Generate code for all prompts."""
|
|
@@ -522,7 +542,10 @@ class CodeGenerator:
|
|
|
522
542
|
|
|
523
543
|
# Create the prompt file
|
|
524
544
|
output_file = prompt_dir / rel_path.name
|
|
525
|
-
|
|
545
|
+
root_file_modules = self._get_root_file_modules()
|
|
546
|
+
transform_component(
|
|
547
|
+
prompt, output_file, self.project_path, self.import_map, root_file_modules=root_file_modules
|
|
548
|
+
)
|
|
526
549
|
|
|
527
550
|
def _get_transport_config(self, transport_type: str) -> dict:
|
|
528
551
|
"""Get transport-specific configuration (primarily for endpoint path display).
|
|
@@ -1596,8 +1619,8 @@ def build_project(
|
|
|
1596
1619
|
shutil.copy2(health_path, output_dir)
|
|
1597
1620
|
console.print(get_status_text("success", "Health script copied to build directory"))
|
|
1598
1621
|
|
|
1599
|
-
# Copy any additional Python files from project root
|
|
1600
|
-
discovered_root_files =
|
|
1622
|
+
# Copy any additional Python files from project root (reuse cached discovery from generator)
|
|
1623
|
+
discovered_root_files = generator._get_cached_root_files()
|
|
1601
1624
|
|
|
1602
1625
|
for filename, file_path in discovered_root_files.items():
|
|
1603
1626
|
dest_path = output_dir / filename
|
|
@@ -1720,8 +1743,6 @@ def discover_root_files(project_path: Path) -> dict[str, Path]:
|
|
|
1720
1743
|
Returns:
|
|
1721
1744
|
Dictionary mapping filenames to their full paths
|
|
1722
1745
|
"""
|
|
1723
|
-
import ast
|
|
1724
|
-
|
|
1725
1746
|
discovered_files = {}
|
|
1726
1747
|
|
|
1727
1748
|
# Files that are handled specially by Golf and should not be auto-copied
|
|
@@ -1757,19 +1778,11 @@ def discover_root_files(project_path: Path) -> dict[str, Path]:
|
|
|
1757
1778
|
if filename.startswith(".") or filename.startswith("_") or filename.endswith("~"):
|
|
1758
1779
|
continue
|
|
1759
1780
|
|
|
1760
|
-
#
|
|
1781
|
+
# Just verify it's a readable file
|
|
1761
1782
|
try:
|
|
1762
1783
|
with open(file_path, encoding="utf-8") as f:
|
|
1763
|
-
#
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
# Basic Python syntax validation
|
|
1767
|
-
try:
|
|
1768
|
-
ast.parse(content + "\n# truncated for validation")
|
|
1769
|
-
except SyntaxError:
|
|
1770
|
-
# Still include files with syntax errors, but warn
|
|
1771
|
-
console.print(f"[yellow]Warning: {filename} has syntax issues but will be included[/yellow]")
|
|
1772
|
-
|
|
1784
|
+
# Just check if file is readable - don't validate syntax
|
|
1785
|
+
f.read(1) # Read one character to verify readability
|
|
1773
1786
|
except (OSError, UnicodeDecodeError) as e:
|
|
1774
1787
|
console.print(f"[yellow]Warning: Cannot read {filename}, skipping: {e}[/yellow]")
|
|
1775
1788
|
continue
|
|
@@ -20,6 +20,7 @@ class ImportTransformer(ast.NodeTransformer):
|
|
|
20
20
|
target_path: Path,
|
|
21
21
|
import_map: dict[str, str],
|
|
22
22
|
project_root: Path,
|
|
23
|
+
root_file_modules: set[str] | None = None,
|
|
23
24
|
) -> None:
|
|
24
25
|
"""Initialize the import transformer.
|
|
25
26
|
|
|
@@ -28,14 +29,61 @@ class ImportTransformer(ast.NodeTransformer):
|
|
|
28
29
|
target_path: Path to the target file
|
|
29
30
|
import_map: Mapping of original module paths to generated paths
|
|
30
31
|
project_root: Root path of the project
|
|
32
|
+
root_file_modules: Set of root file module names (without .py extension)
|
|
31
33
|
"""
|
|
32
34
|
self.original_path = original_path
|
|
33
35
|
self.target_path = target_path
|
|
34
36
|
self.import_map = import_map
|
|
35
37
|
self.project_root = project_root
|
|
38
|
+
self.root_file_modules = root_file_modules or set()
|
|
39
|
+
|
|
40
|
+
def _calculate_import_depth(self) -> int:
|
|
41
|
+
"""Calculate the relative import depth needed to reach build root from component location."""
|
|
42
|
+
try:
|
|
43
|
+
# Get component path relative to project root
|
|
44
|
+
relative_path = self.target_path.relative_to(self.project_root)
|
|
45
|
+
|
|
46
|
+
# Count directory levels: components/tools/weather.py = 2 levels, needs level=2
|
|
47
|
+
# components/tools/api/handler.py = 3 levels, needs level=3
|
|
48
|
+
# Build root contains the root files, so depth = number of path parts
|
|
49
|
+
return len(relative_path.parts) - 1 # Subtract 1 for the filename itself
|
|
50
|
+
|
|
51
|
+
except ValueError:
|
|
52
|
+
# Fallback to level=3 if path calculation fails
|
|
53
|
+
return 3
|
|
36
54
|
|
|
37
55
|
def visit_Import(self, node: ast.Import) -> Any:
|
|
38
56
|
"""Transform import statements."""
|
|
57
|
+
new_names = []
|
|
58
|
+
|
|
59
|
+
for alias in node.names:
|
|
60
|
+
module_name = alias.name
|
|
61
|
+
|
|
62
|
+
if module_name in self.root_file_modules:
|
|
63
|
+
# Calculate dynamic depth based on component location
|
|
64
|
+
depth = self._calculate_import_depth()
|
|
65
|
+
|
|
66
|
+
# Convert to from-import: import config -> from ...config import config
|
|
67
|
+
# Or if there's an asname: import config as cfg -> from ...config import config as cfg
|
|
68
|
+
if alias.asname:
|
|
69
|
+
# import config as cfg -> from ...config import config as cfg
|
|
70
|
+
import_name = module_name
|
|
71
|
+
asname = alias.asname
|
|
72
|
+
else:
|
|
73
|
+
# import config -> from ...config import config
|
|
74
|
+
import_name = module_name
|
|
75
|
+
asname = None
|
|
76
|
+
|
|
77
|
+
# Return a from-import instead of continuing with import
|
|
78
|
+
return ast.ImportFrom(
|
|
79
|
+
module=module_name, names=[ast.alias(name=import_name, asname=asname)], level=depth
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
new_names.append(alias)
|
|
83
|
+
|
|
84
|
+
# If no root modules, return original or modified import
|
|
85
|
+
if new_names != list(node.names):
|
|
86
|
+
return ast.Import(names=new_names)
|
|
39
87
|
return node
|
|
40
88
|
|
|
41
89
|
def visit_ImportFrom(self, node: ast.ImportFrom) -> Any:
|
|
@@ -43,6 +91,12 @@ class ImportTransformer(ast.NodeTransformer):
|
|
|
43
91
|
if node.module is None:
|
|
44
92
|
return node
|
|
45
93
|
|
|
94
|
+
# Check if this is importing from a root file module
|
|
95
|
+
if node.level == 0 and node.module in self.root_file_modules:
|
|
96
|
+
# Calculate dynamic depth instead of using hardcoded level=3
|
|
97
|
+
depth = self._calculate_import_depth()
|
|
98
|
+
return ast.ImportFrom(module=node.module, names=node.names, level=depth)
|
|
99
|
+
|
|
46
100
|
# Handle relative imports
|
|
47
101
|
if node.level > 0:
|
|
48
102
|
# Calculate the source module path
|
|
@@ -98,6 +152,7 @@ def transform_component(
|
|
|
98
152
|
project_path: Path,
|
|
99
153
|
import_map: dict[str, str],
|
|
100
154
|
source_file: Path | None = None,
|
|
155
|
+
root_file_modules: set[str] | None = None,
|
|
101
156
|
) -> str:
|
|
102
157
|
"""Transform a GolfMCP component into a standalone FastMCP component.
|
|
103
158
|
|
|
@@ -107,6 +162,7 @@ def transform_component(
|
|
|
107
162
|
project_path: Path to the project root
|
|
108
163
|
import_map: Mapping of original module paths to generated paths
|
|
109
164
|
source_file: Optional path to source file (for shared files)
|
|
165
|
+
root_file_modules: Set of root file module names (without .py extension)
|
|
110
166
|
|
|
111
167
|
Returns:
|
|
112
168
|
Generated component code
|
|
@@ -126,7 +182,7 @@ def transform_component(
|
|
|
126
182
|
tree = ast.parse(source_code)
|
|
127
183
|
|
|
128
184
|
# Transform imports
|
|
129
|
-
transformer = ImportTransformer(file_path, output_file, import_map, project_path)
|
|
185
|
+
transformer = ImportTransformer(file_path, output_file, import_map, project_path, root_file_modules)
|
|
130
186
|
tree = transformer.visit(tree)
|
|
131
187
|
|
|
132
188
|
# Get all imports and docstring
|
|
@@ -147,13 +203,10 @@ def transform_component(
|
|
|
147
203
|
if isinstance(node, ast.Import | ast.ImportFrom):
|
|
148
204
|
imports.append(node)
|
|
149
205
|
|
|
150
|
-
#
|
|
151
|
-
|
|
206
|
+
# Build full transformed code - start with docstring first (Python convention)
|
|
207
|
+
transformed_code = ""
|
|
152
208
|
|
|
153
|
-
#
|
|
154
|
-
transformed_code = transformed_imports + "\n\n"
|
|
155
|
-
|
|
156
|
-
# Add docstring if present, using proper triple quotes for multi-line docstrings
|
|
209
|
+
# Add docstring first if present, using proper triple quotes for multi-line docstrings
|
|
157
210
|
if docstring:
|
|
158
211
|
# Check if docstring contains newlines
|
|
159
212
|
if "\n" in docstring:
|
|
@@ -163,6 +216,11 @@ def transform_component(
|
|
|
163
216
|
# Use single quotes for single-line docstrings
|
|
164
217
|
transformed_code += f'"{docstring}"\n\n'
|
|
165
218
|
|
|
219
|
+
# Add transformed imports after docstring
|
|
220
|
+
if imports:
|
|
221
|
+
transformed_imports = ast.unparse(ast.Module(body=imports, type_ignores=[]))
|
|
222
|
+
transformed_code += transformed_imports + "\n\n"
|
|
223
|
+
|
|
166
224
|
# Add the rest of the code except imports and the original docstring
|
|
167
225
|
remaining_nodes = []
|
|
168
226
|
for node in tree.body:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.13"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|