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.

Files changed (59) hide show
  1. {golf_mcp-0.2.13/src/golf_mcp.egg-info → golf_mcp-0.2.15}/PKG-INFO +1 -1
  2. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/pyproject.toml +2 -2
  3. golf_mcp-0.2.15/src/golf/__init__.py +1 -0
  4. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder.py +32 -19
  5. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/transformer.py +65 -7
  6. {golf_mcp-0.2.13 → golf_mcp-0.2.15/src/golf_mcp.egg-info}/PKG-INFO +1 -1
  7. golf_mcp-0.2.13/src/golf/__init__.py +0 -1
  8. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/LICENSE +0 -0
  9. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/MANIFEST.in +0 -0
  10. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/README.md +0 -0
  11. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/setup.cfg +0 -0
  12. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/__init__.py +0 -0
  13. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/api_key.py +0 -0
  14. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/factory.py +0 -0
  15. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/helpers.py +0 -0
  16. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/providers.py +0 -0
  17. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/auth/registry.py +0 -0
  18. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/cli/__init__.py +0 -0
  19. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/cli/branding.py +0 -0
  20. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/cli/main.py +0 -0
  21. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/__init__.py +0 -0
  22. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/build.py +0 -0
  23. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/init.py +0 -0
  24. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/commands/run.py +0 -0
  25. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/__init__.py +0 -0
  26. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder_auth.py +0 -0
  27. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder_metrics.py +0 -0
  28. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/builder_telemetry.py +0 -0
  29. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/config.py +0 -0
  30. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/parser.py +0 -0
  31. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/core/telemetry.py +0 -0
  32. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/__init__.py +0 -0
  33. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/.env.example +0 -0
  34. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/README.md +0 -0
  35. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/auth.py +0 -0
  36. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/golf.json +0 -0
  37. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/prompts/welcome.py +0 -0
  38. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/current_time.py +0 -0
  39. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/info.py +0 -0
  40. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/city.py +0 -0
  41. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/client.py +0 -0
  42. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/current.py +0 -0
  43. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
  44. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/tools/calculator.py +0 -0
  45. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/examples/basic/tools/say/hello.py +0 -0
  46. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/metrics/__init__.py +0 -0
  47. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/metrics/collector.py +0 -0
  48. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/metrics/registry.py +0 -0
  49. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/telemetry/__init__.py +0 -0
  50. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/telemetry/instrumentation.py +0 -0
  51. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/__init__.py +0 -0
  52. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/context.py +0 -0
  53. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/elicitation.py +0 -0
  54. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf/utilities/sampling.py +0 -0
  55. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/SOURCES.txt +0 -0
  56. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  57. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  58. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/requires.txt +0 -0
  59. {golf_mcp-0.2.13 → golf_mcp-0.2.15}/src/golf_mcp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.2.13
3
+ Version: 0.2.15
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "golf-mcp"
7
- version = "0.2.13"
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.13"
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 = discover_root_files(self.project_path)
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
- transform_component(tool, output_file, self.project_path, self.import_map)
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
- transform_component(resource, output_file, self.project_path, self.import_map)
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
- transform_component(prompt, output_file, self.project_path, self.import_map)
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 = discover_root_files(project_path)
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
- # Validate it's a readable Python file
1781
+ # Just verify it's a readable file
1761
1782
  try:
1762
1783
  with open(file_path, encoding="utf-8") as f:
1763
- # Read first 200 chars to validate it's a proper Python file
1764
- content = f.read(200)
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
- # Generate the transformed code
151
- transformed_imports = ast.unparse(ast.Module(body=imports, type_ignores=[]))
206
+ # Build full transformed code - start with docstring first (Python convention)
207
+ transformed_code = ""
152
208
 
153
- # Build full transformed code
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.2.13
3
+ Version: 0.2.15
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -1 +0,0 @@
1
- __version__ = "0.2.13"
File without changes
File without changes
File without changes
File without changes