golf-mcp 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of golf-mcp might be problematic. Click here for more details.
- golf/__init__.py +1 -1
- golf/auth/__init__.py +38 -26
- golf/auth/api_key.py +16 -23
- golf/auth/helpers.py +68 -54
- golf/auth/oauth.py +340 -277
- golf/auth/provider.py +58 -53
- golf/cli/__init__.py +1 -1
- golf/cli/main.py +209 -87
- golf/commands/__init__.py +1 -1
- golf/commands/build.py +31 -25
- golf/commands/init.py +81 -53
- golf/commands/run.py +30 -15
- golf/core/__init__.py +1 -1
- golf/core/builder.py +493 -362
- golf/core/builder_auth.py +115 -107
- golf/core/builder_telemetry.py +12 -9
- golf/core/config.py +62 -46
- golf/core/parser.py +174 -136
- golf/core/telemetry.py +216 -95
- golf/core/transformer.py +53 -55
- golf/examples/__init__.py +0 -1
- golf/examples/api_key/pre_build.py +2 -2
- golf/examples/api_key/tools/issues/create.py +35 -36
- golf/examples/api_key/tools/issues/list.py +42 -37
- golf/examples/api_key/tools/repos/list.py +50 -29
- golf/examples/api_key/tools/search/code.py +50 -37
- golf/examples/api_key/tools/users/get.py +21 -20
- golf/examples/basic/pre_build.py +4 -4
- golf/examples/basic/prompts/welcome.py +6 -7
- golf/examples/basic/resources/current_time.py +10 -9
- golf/examples/basic/resources/info.py +6 -5
- golf/examples/basic/resources/weather/common.py +16 -10
- golf/examples/basic/resources/weather/current.py +15 -11
- golf/examples/basic/resources/weather/forecast.py +15 -11
- golf/examples/basic/tools/github_user.py +19 -21
- golf/examples/basic/tools/hello.py +10 -6
- golf/examples/basic/tools/payments/charge.py +34 -25
- golf/examples/basic/tools/payments/common.py +8 -6
- golf/examples/basic/tools/payments/refund.py +29 -25
- golf/telemetry/__init__.py +6 -6
- golf/telemetry/instrumentation.py +455 -310
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/METADATA +1 -1
- golf_mcp-0.1.13.dist-info/RECORD +55 -0
- golf_mcp-0.1.11.dist-info/RECORD +0 -55
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/top_level.txt +0 -0
golf/commands/build.py
CHANGED
|
@@ -4,26 +4,26 @@ This module implements the `golf build` command which generates a standalone
|
|
|
4
4
|
FastMCP application from a GolfMCP project.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from pathlib import Path
|
|
8
7
|
import argparse
|
|
8
|
+
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from rich.console import Console
|
|
11
11
|
|
|
12
|
-
from golf.core.config import Settings, load_settings
|
|
13
12
|
from golf.core.builder import build_project as core_build_project
|
|
13
|
+
from golf.core.config import Settings, load_settings
|
|
14
14
|
|
|
15
15
|
console = Console()
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def build_project(
|
|
19
|
-
project_path: Path,
|
|
20
|
-
settings: Settings,
|
|
19
|
+
project_path: Path,
|
|
20
|
+
settings: Settings,
|
|
21
21
|
output_dir: Path,
|
|
22
22
|
build_env: str = "prod",
|
|
23
|
-
copy_env: bool = False
|
|
23
|
+
copy_env: bool = False,
|
|
24
24
|
) -> None:
|
|
25
25
|
"""Build a standalone FastMCP application from a GolfMCP project.
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
Args:
|
|
28
28
|
project_path: Path to the project root
|
|
29
29
|
settings: Project settings
|
|
@@ -32,47 +32,53 @@ def build_project(
|
|
|
32
32
|
copy_env: Whether to copy environment variables to the built app
|
|
33
33
|
"""
|
|
34
34
|
# Call the centralized build function from core.builder
|
|
35
|
-
core_build_project(
|
|
35
|
+
core_build_project(
|
|
36
|
+
project_path, settings, output_dir, build_env=build_env, copy_env=copy_env
|
|
37
|
+
)
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
# Add a main section to run the build_project function when this module is executed directly
|
|
39
41
|
if __name__ == "__main__":
|
|
40
|
-
parser = argparse.ArgumentParser(
|
|
42
|
+
parser = argparse.ArgumentParser(
|
|
43
|
+
description="Build a standalone FastMCP application"
|
|
44
|
+
)
|
|
41
45
|
parser.add_argument(
|
|
42
|
-
"--project-path",
|
|
43
|
-
|
|
46
|
+
"--project-path",
|
|
47
|
+
"-p",
|
|
48
|
+
type=Path,
|
|
44
49
|
default=Path.cwd(),
|
|
45
|
-
help="Path to the project root (default: current directory)"
|
|
50
|
+
help="Path to the project root (default: current directory)",
|
|
46
51
|
)
|
|
47
52
|
parser.add_argument(
|
|
48
|
-
"--output-dir",
|
|
49
|
-
|
|
53
|
+
"--output-dir",
|
|
54
|
+
"-o",
|
|
55
|
+
type=Path,
|
|
50
56
|
default=Path.cwd() / "dist",
|
|
51
|
-
help="Directory to output the built project (default: ./dist)"
|
|
57
|
+
help="Directory to output the built project (default: ./dist)",
|
|
52
58
|
)
|
|
53
59
|
parser.add_argument(
|
|
54
60
|
"--build-env",
|
|
55
61
|
type=str,
|
|
56
62
|
default="prod",
|
|
57
63
|
choices=["dev", "prod"],
|
|
58
|
-
help="Build environment to use (default: prod)"
|
|
64
|
+
help="Build environment to use (default: prod)",
|
|
59
65
|
)
|
|
60
66
|
parser.add_argument(
|
|
61
67
|
"--copy-env",
|
|
62
68
|
action="store_true",
|
|
63
|
-
help="Copy environment variables to the built application"
|
|
69
|
+
help="Copy environment variables to the built application",
|
|
64
70
|
)
|
|
65
|
-
|
|
71
|
+
|
|
66
72
|
args = parser.parse_args()
|
|
67
|
-
|
|
73
|
+
|
|
68
74
|
# Load settings from the project path
|
|
69
75
|
settings = load_settings(args.project_path)
|
|
70
|
-
|
|
76
|
+
|
|
71
77
|
# Execute the build
|
|
72
78
|
build_project(
|
|
73
|
-
args.project_path,
|
|
74
|
-
settings,
|
|
75
|
-
args.output_dir,
|
|
76
|
-
build_env=args.build_env,
|
|
77
|
-
copy_env=args.copy_env
|
|
78
|
-
)
|
|
79
|
+
args.project_path,
|
|
80
|
+
settings,
|
|
81
|
+
args.output_dir,
|
|
82
|
+
build_env=args.build_env,
|
|
83
|
+
copy_env=args.copy_env,
|
|
84
|
+
)
|
golf/commands/init.py
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
"""Project initialization command implementation."""
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import shutil
|
|
5
4
|
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
5
|
|
|
8
6
|
from rich.console import Console
|
|
9
7
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
8
|
from rich.prompt import Confirm
|
|
11
9
|
|
|
12
|
-
from golf.core.telemetry import
|
|
10
|
+
from golf.core.telemetry import track_command, track_event
|
|
13
11
|
|
|
14
12
|
console = Console()
|
|
15
13
|
|
|
@@ -20,7 +18,7 @@ def initialize_project(
|
|
|
20
18
|
template: str = "basic",
|
|
21
19
|
) -> None:
|
|
22
20
|
"""Initialize a new GolfMCP project with the specified template.
|
|
23
|
-
|
|
21
|
+
|
|
24
22
|
Args:
|
|
25
23
|
project_name: Name of the project
|
|
26
24
|
output_dir: Directory where the project will be created
|
|
@@ -32,45 +30,60 @@ def initialize_project(
|
|
|
32
30
|
if template not in valid_templates:
|
|
33
31
|
console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
|
|
34
32
|
console.print(f"Available templates: {', '.join(valid_templates)}")
|
|
35
|
-
track_command(
|
|
33
|
+
track_command(
|
|
34
|
+
"init",
|
|
35
|
+
success=False,
|
|
36
|
+
error_type="InvalidTemplate",
|
|
37
|
+
error_message=f"Unknown template: {template}",
|
|
38
|
+
)
|
|
36
39
|
return
|
|
37
|
-
|
|
40
|
+
|
|
38
41
|
# Check if directory exists
|
|
39
42
|
if output_dir.exists():
|
|
40
43
|
if not output_dir.is_dir():
|
|
41
44
|
console.print(
|
|
42
45
|
f"[bold red]Error:[/bold red] '{output_dir}' exists but is not a directory."
|
|
43
46
|
)
|
|
44
|
-
track_command(
|
|
47
|
+
track_command(
|
|
48
|
+
"init",
|
|
49
|
+
success=False,
|
|
50
|
+
error_type="NotADirectory",
|
|
51
|
+
error_message="Target exists but is not a directory",
|
|
52
|
+
)
|
|
45
53
|
return
|
|
46
|
-
|
|
54
|
+
|
|
47
55
|
# Check if directory is empty
|
|
48
|
-
if any(output_dir.iterdir())
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
+
if any(output_dir.iterdir()) and not Confirm.ask(
|
|
57
|
+
f"Directory '{output_dir}' is not empty. Continue anyway?",
|
|
58
|
+
default=False,
|
|
59
|
+
):
|
|
60
|
+
console.print("Initialization cancelled.")
|
|
61
|
+
track_event("cli_init_cancelled", {"success": False})
|
|
62
|
+
return
|
|
56
63
|
else:
|
|
57
64
|
# Create the directory
|
|
58
65
|
output_dir.mkdir(parents=True)
|
|
59
|
-
|
|
66
|
+
|
|
60
67
|
# Find template directory within the installed package
|
|
61
68
|
import golf
|
|
69
|
+
|
|
62
70
|
package_init_file = Path(golf.__file__)
|
|
63
71
|
# The 'examples' directory is now inside the 'golf' package directory
|
|
64
72
|
# e.g. golf/examples/basic, so go up one from __init__.py to get to 'golf'
|
|
65
73
|
template_dir = package_init_file.parent / "examples" / template
|
|
66
|
-
|
|
74
|
+
|
|
67
75
|
if not template_dir.exists():
|
|
68
76
|
console.print(
|
|
69
77
|
f"[bold red]Error:[/bold red] Could not find template '{template}'"
|
|
70
78
|
)
|
|
71
|
-
track_command(
|
|
79
|
+
track_command(
|
|
80
|
+
"init",
|
|
81
|
+
success=False,
|
|
82
|
+
error_type="TemplateNotFound",
|
|
83
|
+
error_message=f"Template directory not found: {template}",
|
|
84
|
+
)
|
|
72
85
|
return
|
|
73
|
-
|
|
86
|
+
|
|
74
87
|
# Copy template files
|
|
75
88
|
with Progress(
|
|
76
89
|
SpinnerColumn(),
|
|
@@ -78,36 +91,37 @@ def initialize_project(
|
|
|
78
91
|
transient=True,
|
|
79
92
|
) as progress:
|
|
80
93
|
progress.add_task("copying", total=None)
|
|
81
|
-
|
|
94
|
+
|
|
82
95
|
# Copy directory structure
|
|
83
96
|
_copy_template(template_dir, output_dir, project_name)
|
|
84
|
-
|
|
97
|
+
|
|
85
98
|
# Create virtual environment
|
|
86
99
|
console.print("[bold green]Project initialized successfully![/bold green]")
|
|
87
|
-
console.print(
|
|
100
|
+
console.print("\nTo get started, run:")
|
|
88
101
|
console.print(f" cd {output_dir.name}")
|
|
89
|
-
console.print(
|
|
90
|
-
|
|
102
|
+
console.print(" golf build dev")
|
|
103
|
+
|
|
91
104
|
# Track successful initialization
|
|
92
|
-
track_event("cli_init_success", {
|
|
93
|
-
"success": True,
|
|
94
|
-
"template": template
|
|
95
|
-
})
|
|
105
|
+
track_event("cli_init_success", {"success": True, "template": template})
|
|
96
106
|
except Exception as e:
|
|
97
107
|
# Capture error details for telemetry
|
|
98
108
|
error_type = type(e).__name__
|
|
99
109
|
error_message = str(e)
|
|
100
|
-
|
|
101
|
-
console.print(
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
|
|
111
|
+
console.print(
|
|
112
|
+
f"[bold red]Error during initialization:[/bold red] {error_message}"
|
|
113
|
+
)
|
|
114
|
+
track_command(
|
|
115
|
+
"init", success=False, error_type=error_type, error_message=error_message
|
|
116
|
+
)
|
|
117
|
+
|
|
104
118
|
# Re-raise to maintain existing behavior
|
|
105
119
|
raise
|
|
106
120
|
|
|
107
121
|
|
|
108
122
|
def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> None:
|
|
109
123
|
"""Copy template files to the target directory, with variable substitution.
|
|
110
|
-
|
|
124
|
+
|
|
111
125
|
Args:
|
|
112
126
|
source_dir: Source template directory
|
|
113
127
|
target_dir: Target project directory
|
|
@@ -117,42 +131,44 @@ def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> Non
|
|
|
117
131
|
(target_dir / "tools").mkdir(exist_ok=True)
|
|
118
132
|
(target_dir / "resources").mkdir(exist_ok=True)
|
|
119
133
|
(target_dir / "prompts").mkdir(exist_ok=True)
|
|
120
|
-
|
|
134
|
+
|
|
121
135
|
# Copy all files from the template
|
|
122
136
|
for source_path in source_dir.glob("**/*"):
|
|
123
137
|
# Skip if directory (we'll create directories as needed)
|
|
124
138
|
if source_path.is_dir():
|
|
125
139
|
continue
|
|
126
|
-
|
|
140
|
+
|
|
127
141
|
# Compute relative path
|
|
128
142
|
rel_path = source_path.relative_to(source_dir)
|
|
129
143
|
target_path = target_dir / rel_path
|
|
130
|
-
|
|
144
|
+
|
|
131
145
|
# Create parent directories if needed
|
|
132
146
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
-
|
|
147
|
+
|
|
134
148
|
# Copy and substitute content for text files
|
|
135
149
|
if _is_text_file(source_path):
|
|
136
|
-
with open(source_path,
|
|
150
|
+
with open(source_path, encoding="utf-8") as f:
|
|
137
151
|
content = f.read()
|
|
138
|
-
|
|
152
|
+
|
|
139
153
|
# Replace template variables
|
|
140
154
|
content = content.replace("{{project_name}}", project_name)
|
|
141
|
-
content = content.replace(
|
|
142
|
-
|
|
155
|
+
content = content.replace(
|
|
156
|
+
"{{project_name_lowercase}}", project_name.lower()
|
|
157
|
+
)
|
|
158
|
+
|
|
143
159
|
with open(target_path, "w", encoding="utf-8") as f:
|
|
144
160
|
f.write(content)
|
|
145
161
|
else:
|
|
146
162
|
# Binary file, just copy
|
|
147
163
|
shutil.copy2(source_path, target_path)
|
|
148
|
-
|
|
164
|
+
|
|
149
165
|
# Create .env file
|
|
150
166
|
env_file = target_dir / ".env"
|
|
151
167
|
with open(env_file, "w", encoding="utf-8") as f:
|
|
152
168
|
f.write(f"GOLF_NAME={project_name}\n")
|
|
153
169
|
f.write("GOLF_HOST=127.0.0.1\n")
|
|
154
170
|
f.write("GOLF_PORT=3000\n")
|
|
155
|
-
|
|
171
|
+
|
|
156
172
|
# Create a .gitignore if it doesn't exist
|
|
157
173
|
gitignore_file = target_dir / ".gitignore"
|
|
158
174
|
if not gitignore_file.exists():
|
|
@@ -193,31 +209,43 @@ def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> Non
|
|
|
193
209
|
|
|
194
210
|
def _is_text_file(path: Path) -> bool:
|
|
195
211
|
"""Check if a file is a text file that needs variable substitution.
|
|
196
|
-
|
|
212
|
+
|
|
197
213
|
Args:
|
|
198
214
|
path: Path to check
|
|
199
|
-
|
|
215
|
+
|
|
200
216
|
Returns:
|
|
201
217
|
True if the file is a text file
|
|
202
218
|
"""
|
|
203
219
|
# List of known text file extensions
|
|
204
220
|
text_extensions = {
|
|
205
|
-
".py",
|
|
206
|
-
".
|
|
221
|
+
".py",
|
|
222
|
+
".md",
|
|
223
|
+
".txt",
|
|
224
|
+
".html",
|
|
225
|
+
".css",
|
|
226
|
+
".js",
|
|
227
|
+
".json",
|
|
228
|
+
".yml",
|
|
229
|
+
".yaml",
|
|
230
|
+
".toml",
|
|
231
|
+
".ini",
|
|
232
|
+
".cfg",
|
|
233
|
+
".env",
|
|
234
|
+
".example",
|
|
207
235
|
}
|
|
208
|
-
|
|
236
|
+
|
|
209
237
|
# Check if the file has a text extension
|
|
210
238
|
if path.suffix in text_extensions:
|
|
211
239
|
return True
|
|
212
|
-
|
|
240
|
+
|
|
213
241
|
# Check specific filenames without extensions
|
|
214
242
|
if path.name in {".gitignore", "README", "LICENSE"}:
|
|
215
243
|
return True
|
|
216
|
-
|
|
244
|
+
|
|
217
245
|
# Try to detect if it's a text file by reading a bit of it
|
|
218
246
|
try:
|
|
219
|
-
with open(path,
|
|
247
|
+
with open(path, encoding="utf-8") as f:
|
|
220
248
|
f.read(1024)
|
|
221
249
|
return True
|
|
222
250
|
except UnicodeDecodeError:
|
|
223
|
-
return False
|
|
251
|
+
return False
|
golf/commands/run.py
CHANGED
|
@@ -4,7 +4,6 @@ import os
|
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Optional
|
|
8
7
|
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
|
|
@@ -16,44 +15,46 @@ console = Console()
|
|
|
16
15
|
def run_server(
|
|
17
16
|
project_path: Path,
|
|
18
17
|
settings: Settings,
|
|
19
|
-
dist_dir:
|
|
20
|
-
host:
|
|
21
|
-
port:
|
|
18
|
+
dist_dir: Path | None = None,
|
|
19
|
+
host: str | None = None,
|
|
20
|
+
port: int | None = None,
|
|
22
21
|
) -> int:
|
|
23
22
|
"""Run the built FastMCP server.
|
|
24
|
-
|
|
23
|
+
|
|
25
24
|
Args:
|
|
26
25
|
project_path: Path to the project root
|
|
27
26
|
settings: Project settings
|
|
28
27
|
dist_dir: Path to the directory containing the built server (defaults to project_path/dist)
|
|
29
28
|
host: Host to bind the server to (overrides settings)
|
|
30
29
|
port: Port to bind the server to (overrides settings)
|
|
31
|
-
|
|
30
|
+
|
|
32
31
|
Returns:
|
|
33
32
|
Process return code
|
|
34
33
|
"""
|
|
35
34
|
# Set default dist directory if not specified
|
|
36
35
|
if dist_dir is None:
|
|
37
36
|
dist_dir = project_path / "dist"
|
|
38
|
-
|
|
37
|
+
|
|
39
38
|
# Check if server file exists
|
|
40
39
|
server_path = dist_dir / "server.py"
|
|
41
40
|
if not server_path.exists():
|
|
42
|
-
console.print(
|
|
41
|
+
console.print(
|
|
42
|
+
f"[bold red]Error: Server file {server_path} not found.[/bold red]"
|
|
43
|
+
)
|
|
43
44
|
return 1
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
# Prepare environment variables
|
|
46
47
|
env = os.environ.copy()
|
|
47
48
|
if host is not None:
|
|
48
49
|
env["HOST"] = host
|
|
49
50
|
elif settings.host:
|
|
50
51
|
env["HOST"] = settings.host
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
if port is not None:
|
|
53
54
|
env["PORT"] = str(port)
|
|
54
55
|
elif settings.port:
|
|
55
56
|
env["PORT"] = str(settings.port)
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
# Run the server
|
|
58
59
|
try:
|
|
59
60
|
# Using subprocess to properly handle signals (Ctrl+C)
|
|
@@ -62,11 +63,25 @@ def run_server(
|
|
|
62
63
|
cwd=dist_dir,
|
|
63
64
|
env=env,
|
|
64
65
|
)
|
|
65
|
-
|
|
66
|
+
|
|
67
|
+
# Provide more context about the exit
|
|
68
|
+
if process.returncode == 0:
|
|
69
|
+
console.print("[green]Server stopped successfully[/green]")
|
|
70
|
+
elif process.returncode == 130:
|
|
71
|
+
console.print("[yellow]Server stopped by user interrupt (Ctrl+C)[/yellow]")
|
|
72
|
+
elif process.returncode == 143:
|
|
73
|
+
console.print("[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]")
|
|
74
|
+
elif process.returncode == 137:
|
|
75
|
+
console.print("[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]")
|
|
76
|
+
elif process.returncode in [1, 2]:
|
|
77
|
+
console.print(f"[red]Server exited with error code {process.returncode}[/red]")
|
|
78
|
+
else:
|
|
79
|
+
console.print(f"[orange]Server exited with code {process.returncode}[/orange]")
|
|
80
|
+
|
|
66
81
|
return process.returncode
|
|
67
82
|
except KeyboardInterrupt:
|
|
68
|
-
console.print("\n[yellow]Server stopped by user[/yellow]")
|
|
69
|
-
return
|
|
83
|
+
console.print("\n[yellow]Server stopped by user (Ctrl+C)[/yellow]")
|
|
84
|
+
return 130 # Standard exit code for SIGINT
|
|
70
85
|
except Exception as e:
|
|
71
86
|
console.print(f"\n[bold red]Error running server:[/bold red] {e}")
|
|
72
|
-
return 1
|
|
87
|
+
return 1
|
golf/core/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"""Core functionality for the GolfMCP framework."""
|
|
1
|
+
"""Core functionality for the GolfMCP framework."""
|