golf-mcp 0.1.16__tar.gz → 0.1.17__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.1.16 → golf_mcp-0.1.17}/.docs/docs.md +2 -2
- {golf_mcp-0.1.16/src/golf_mcp.egg-info → golf_mcp-0.1.17}/PKG-INFO +8 -3
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/README.md +6 -1
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/pyproject.toml +4 -4
- golf_mcp-0.1.17/src/golf/__init__.py +1 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/cli/main.py +13 -2
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/commands/init.py +63 -1
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/core/builder.py +139 -60
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/core/config.py +6 -0
- golf_mcp-0.1.17/src/golf/core/parser.py +1059 -0
- golf_mcp-0.1.17/src/golf/core/platform.py +180 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/core/telemetry.py +28 -8
- golf_mcp-0.1.17/src/golf/examples/api_key/.env.example +1 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/README.md +10 -10
- golf_mcp-0.1.17/src/golf/examples/api_key/golf.json +8 -0
- golf_mcp-0.1.17/src/golf/examples/basic/.env.example +4 -0
- golf_mcp-0.1.17/src/golf/examples/basic/golf.json +8 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/telemetry/instrumentation.py +26 -48
- {golf_mcp-0.1.16 → golf_mcp-0.1.17/src/golf_mcp.egg-info}/PKG-INFO +8 -3
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/SOURCES.txt +1 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/requires.txt +1 -1
- golf_mcp-0.1.16/src/golf/__init__.py +0 -1
- golf_mcp-0.1.16/src/golf/core/parser.py +0 -560
- golf_mcp-0.1.16/src/golf/examples/api_key/.env.example +0 -5
- golf_mcp-0.1.16/src/golf/examples/api_key/golf.json +0 -12
- golf_mcp-0.1.16/src/golf/examples/basic/.env.example +0 -5
- golf_mcp-0.1.16/src/golf/examples/basic/golf.json +0 -12
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/.docs/fast-mcp.md +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/.docs/fastmcp-example-1.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/.docs/fastmcp-example-2.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/.docs/mcp.md +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/.docs/oauth-implementation.md +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/.docs/oauth.md +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/LICENSE +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/MANIFEST.in +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/setup.cfg +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/auth/__init__.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/auth/api_key.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/auth/helpers.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/auth/oauth.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/auth/provider.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/commands/run.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/core/builder_auth.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/core/transformer.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/.env +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/pre_build.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/issues/create.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/issues/list.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/repos/list.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/search/code.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/users/get.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/.env +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/pre_build.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/weather/common.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/github_user.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/hello.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/payments/charge.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/payments/common.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/payments/refund.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf/telemetry/__init__.py +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.1.16 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/top_level.txt +0 -0
|
@@ -49,7 +49,7 @@ Given a component file `C` with absolute path
|
|
|
49
49
|
let `PathRev = [pₙ, …, p₁]` (reverse order of parent dirs under the category). The **ID** is
|
|
50
50
|
|
|
51
51
|
```
|
|
52
|
-
<filename> + ("
|
|
52
|
+
<filename> + ("_" + "_".join(PathRev) if PathRev else "")
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
### Formal Definition (BNF)
|
|
@@ -190,7 +190,7 @@ def run(charge_id: str, amount: int) -> dict:
|
|
|
190
190
|
from tools.payments.refund import submit as _submit
|
|
191
191
|
|
|
192
192
|
mcp.tool(
|
|
193
|
-
name="
|
|
193
|
+
name="submit_refund_payments",
|
|
194
194
|
description="Submit a refund request to Stripe."
|
|
195
195
|
)(_submit.run)
|
|
196
196
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: golf-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.17
|
|
4
4
|
Summary: Framework for building MCP servers
|
|
5
5
|
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: typer>=0.15.4
|
|
23
23
|
Requires-Dist: rich>=14.0.0
|
|
24
|
-
Requires-Dist: fastmcp
|
|
24
|
+
Requires-Dist: fastmcp<2.6.0,>=2.0.0
|
|
25
25
|
Requires-Dist: pydantic>=2.11.0
|
|
26
26
|
Requires-Dist: python-dotenv>=1.1.0
|
|
27
27
|
Requires-Dist: black>=24.10.0
|
|
@@ -128,7 +128,7 @@ A Golf project initialized with `golf init` will have a structure similar to thi
|
|
|
128
128
|
|
|
129
129
|
- **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
|
|
130
130
|
- **`tools/`**, **`resources/`**, **`prompts/`**: Contain your Python files, each defining a single component. These directories can also contain nested subdirectories to further organize your components (e.g., `tools/payments/charge.py`). The module docstring of each file serves as the component's description.
|
|
131
|
-
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `
|
|
131
|
+
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit_payments` (filename, followed by reversed parent directories under the main category, joined by underscores).
|
|
132
132
|
- **`common.py`** (not shown, but can be placed in subdirectories like `tools/payments/common.py`): Used to share code (clients, models, etc.) among components in the same subdirectory.
|
|
133
133
|
|
|
134
134
|
## Example: Defining a Tool
|
|
@@ -178,6 +178,10 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
178
178
|
// - "streamable-http": HTTP with streaming support
|
|
179
179
|
// - "stdio": Standard I/O (for CLI integration)
|
|
180
180
|
|
|
181
|
+
// HTTP Transport Configuration (optional)
|
|
182
|
+
"stateless_http": false, // Make streamable-http transport stateless (new session per request)
|
|
183
|
+
// When true, server restarts won't break existing client connections
|
|
184
|
+
|
|
181
185
|
// Health Check Configuration (optional)
|
|
182
186
|
"health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
|
|
183
187
|
"health_check_path": "/health", // HTTP path for health check endpoint
|
|
@@ -198,6 +202,7 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
198
202
|
- `"streamable-http"` provides HTTP streaming for traditional API clients
|
|
199
203
|
- `"stdio"` enables integration with command-line tools and scripts
|
|
200
204
|
- **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
|
|
205
|
+
- **`stateless_http`**: When true, makes the streamable-http transport stateless by creating a new session for each request. This ensures that server restarts don't break existing client connections, making the server truly stateless.
|
|
201
206
|
- **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
|
|
202
207
|
- **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
|
|
203
208
|
- **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
|
|
@@ -90,7 +90,7 @@ A Golf project initialized with `golf init` will have a structure similar to thi
|
|
|
90
90
|
|
|
91
91
|
- **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
|
|
92
92
|
- **`tools/`**, **`resources/`**, **`prompts/`**: Contain your Python files, each defining a single component. These directories can also contain nested subdirectories to further organize your components (e.g., `tools/payments/charge.py`). The module docstring of each file serves as the component's description.
|
|
93
|
-
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `
|
|
93
|
+
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit_payments` (filename, followed by reversed parent directories under the main category, joined by underscores).
|
|
94
94
|
- **`common.py`** (not shown, but can be placed in subdirectories like `tools/payments/common.py`): Used to share code (clients, models, etc.) among components in the same subdirectory.
|
|
95
95
|
|
|
96
96
|
## Example: Defining a Tool
|
|
@@ -140,6 +140,10 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
140
140
|
// - "streamable-http": HTTP with streaming support
|
|
141
141
|
// - "stdio": Standard I/O (for CLI integration)
|
|
142
142
|
|
|
143
|
+
// HTTP Transport Configuration (optional)
|
|
144
|
+
"stateless_http": false, // Make streamable-http transport stateless (new session per request)
|
|
145
|
+
// When true, server restarts won't break existing client connections
|
|
146
|
+
|
|
143
147
|
// Health Check Configuration (optional)
|
|
144
148
|
"health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
|
|
145
149
|
"health_check_path": "/health", // HTTP path for health check endpoint
|
|
@@ -160,6 +164,7 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
160
164
|
- `"streamable-http"` provides HTTP streaming for traditional API clients
|
|
161
165
|
- `"stdio"` enables integration with command-line tools and scripts
|
|
162
166
|
- **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
|
|
167
|
+
- **`stateless_http`**: When true, makes the streamable-http transport stateless by creating a new session for each request. This ensures that server restarts don't break existing client connections, making the server truly stateless.
|
|
163
168
|
- **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
|
|
164
169
|
- **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
|
|
165
170
|
- **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "golf-mcp"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.17"
|
|
8
8
|
description = "Framework for building MCP servers"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Antoni Gmitruk", email = "antoni@golf.dev"}
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
dependencies = [
|
|
29
29
|
"typer>=0.15.4",
|
|
30
30
|
"rich>=14.0.0",
|
|
31
|
-
"fastmcp>=2.0.0",
|
|
31
|
+
"fastmcp>=2.0.0,<2.6.0",
|
|
32
32
|
"pydantic>=2.11.0",
|
|
33
33
|
"python-dotenv>=1.1.0",
|
|
34
34
|
"black>=24.10.0",
|
|
@@ -64,7 +64,7 @@ golf = ["examples/**/*"]
|
|
|
64
64
|
|
|
65
65
|
[tool.poetry]
|
|
66
66
|
name = "golf-mcp"
|
|
67
|
-
version = "0.1.
|
|
67
|
+
version = "0.1.17"
|
|
68
68
|
description = "Framework for building MCP servers with zero boilerplate"
|
|
69
69
|
authors = ["Antoni Gmitruk <antoni@golf.dev>"]
|
|
70
70
|
license = "Apache-2.0"
|
|
@@ -86,7 +86,7 @@ classifiers = [
|
|
|
86
86
|
|
|
87
87
|
[tool.poetry.dependencies]
|
|
88
88
|
python = ">=3.8" # Match requires-python
|
|
89
|
-
fastmcp = ">=2.0.0"
|
|
89
|
+
fastmcp = ">=2.0.0,<2.6.0"
|
|
90
90
|
typer = {extras = ["all"], version = ">=0.15.4"}
|
|
91
91
|
pydantic = ">=2.11.0"
|
|
92
92
|
rich = ">=14.0.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.17"
|
|
@@ -121,7 +121,11 @@ def build_dev(
|
|
|
121
121
|
None, "--output-dir", "-o", help="Directory to output the built project"
|
|
122
122
|
),
|
|
123
123
|
) -> None:
|
|
124
|
-
"""Build a development version with environment variables copied.
|
|
124
|
+
"""Build a development version with app environment variables copied.
|
|
125
|
+
|
|
126
|
+
Golf credentials (GOLF_*) are always loaded from .env for build operations.
|
|
127
|
+
All environment variables are copied to the built project for development.
|
|
128
|
+
"""
|
|
125
129
|
# Find project root directory
|
|
126
130
|
project_root, config_path = find_project_root()
|
|
127
131
|
|
|
@@ -173,7 +177,14 @@ def build_prod(
|
|
|
173
177
|
None, "--output-dir", "-o", help="Directory to output the built project"
|
|
174
178
|
),
|
|
175
179
|
) -> None:
|
|
176
|
-
"""Build a production version
|
|
180
|
+
"""Build a production version for deployment.
|
|
181
|
+
|
|
182
|
+
Golf credentials (GOLF_*) are always loaded from .env for build operations
|
|
183
|
+
(platform registration, resource updates). App environment variables are
|
|
184
|
+
NOT copied for security - provide them in your deployment environment.
|
|
185
|
+
|
|
186
|
+
Your production deployment must include GOLF_* vars for runtime telemetry.
|
|
187
|
+
"""
|
|
177
188
|
# Find project root directory
|
|
178
189
|
project_root, config_path = find_project_root()
|
|
179
190
|
|
|
@@ -7,7 +7,12 @@ from rich.console import Console
|
|
|
7
7
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
8
8
|
from rich.prompt import Confirm
|
|
9
9
|
|
|
10
|
-
from golf.core.telemetry import
|
|
10
|
+
from golf.core.telemetry import (
|
|
11
|
+
track_command,
|
|
12
|
+
track_event,
|
|
13
|
+
set_telemetry_enabled,
|
|
14
|
+
load_telemetry_preference,
|
|
15
|
+
)
|
|
11
16
|
|
|
12
17
|
console = Console()
|
|
13
18
|
|
|
@@ -95,6 +100,9 @@ def initialize_project(
|
|
|
95
100
|
# Copy directory structure
|
|
96
101
|
_copy_template(template_dir, output_dir, project_name)
|
|
97
102
|
|
|
103
|
+
# Ask for telemetry consent
|
|
104
|
+
_prompt_for_telemetry_consent()
|
|
105
|
+
|
|
98
106
|
# Create virtual environment
|
|
99
107
|
console.print("[bold green]Project initialized successfully![/bold green]")
|
|
100
108
|
console.print("\nTo get started, run:")
|
|
@@ -207,6 +215,60 @@ def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> Non
|
|
|
207
215
|
f.write("dist/\n")
|
|
208
216
|
|
|
209
217
|
|
|
218
|
+
def _prompt_for_telemetry_consent() -> None:
|
|
219
|
+
"""Prompt user for telemetry consent and save their preference."""
|
|
220
|
+
import os
|
|
221
|
+
|
|
222
|
+
# Skip prompt in test mode, when telemetry is explicitly disabled, or if preference already exists
|
|
223
|
+
if os.environ.get("GOLF_TEST_MODE", "").lower() in ("1", "true", "yes", "on"):
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# Skip if telemetry is explicitly disabled in environment
|
|
227
|
+
if os.environ.get("GOLF_TELEMETRY", "").lower() in ("0", "false", "no", "off"):
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
# Check if user already has a saved preference
|
|
231
|
+
existing_preference = load_telemetry_preference()
|
|
232
|
+
if existing_preference is not None:
|
|
233
|
+
return # User already made a choice
|
|
234
|
+
|
|
235
|
+
console.print()
|
|
236
|
+
console.rule("[bold blue]Anonymous usage analytics[/bold blue]", style="blue")
|
|
237
|
+
console.print()
|
|
238
|
+
console.print(
|
|
239
|
+
"Golf can collect [bold]anonymous usage analytics[/bold] to help improve the tool."
|
|
240
|
+
)
|
|
241
|
+
console.print()
|
|
242
|
+
console.print("[dim]What we collect:[/dim]")
|
|
243
|
+
console.print(" • Command usage (init, build, run)")
|
|
244
|
+
console.print(" • Error types (to fix bugs)")
|
|
245
|
+
console.print(" • Golf version and Python version")
|
|
246
|
+
console.print(" • Operating system type")
|
|
247
|
+
console.print()
|
|
248
|
+
console.print("[dim]What we DON'T collect:[/dim]")
|
|
249
|
+
console.print(" • Your code or project content")
|
|
250
|
+
console.print(" • File paths or project names")
|
|
251
|
+
console.print(" • Personal information")
|
|
252
|
+
console.print(" • IP addresses")
|
|
253
|
+
console.print()
|
|
254
|
+
console.print(
|
|
255
|
+
"You can change this anytime by setting GOLF_TELEMETRY=0 in your environment."
|
|
256
|
+
)
|
|
257
|
+
console.print()
|
|
258
|
+
|
|
259
|
+
enable_telemetry = Confirm.ask(
|
|
260
|
+
"[bold]Enable anonymous usage analytics?[/bold]", default=False
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
set_telemetry_enabled(enable_telemetry, persist=True)
|
|
264
|
+
|
|
265
|
+
if enable_telemetry:
|
|
266
|
+
console.print("[green]✓[/green] Anonymous analytics enabled")
|
|
267
|
+
else:
|
|
268
|
+
console.print("[yellow]○[/yellow] Anonymous analytics disabled")
|
|
269
|
+
console.print()
|
|
270
|
+
|
|
271
|
+
|
|
210
272
|
def _is_text_file(path: Path) -> bool:
|
|
211
273
|
"""Check if a file is a text file that needs variable substitution.
|
|
212
274
|
|
|
@@ -561,14 +561,6 @@ class CodeGenerator:
|
|
|
561
561
|
# Add OpenTelemetry imports if enabled
|
|
562
562
|
if self.settings.opentelemetry_enabled:
|
|
563
563
|
imports.extend(generate_telemetry_imports())
|
|
564
|
-
imports.append("")
|
|
565
|
-
|
|
566
|
-
# Add imports section for different transport methods
|
|
567
|
-
if self.settings.transport == "sse" or self.settings.transport in [
|
|
568
|
-
"streamable-http",
|
|
569
|
-
"http",
|
|
570
|
-
]:
|
|
571
|
-
imports.append("import uvicorn")
|
|
572
564
|
|
|
573
565
|
# Add health check imports if enabled
|
|
574
566
|
if self.settings.health_check_enabled:
|
|
@@ -664,7 +656,10 @@ class CodeGenerator:
|
|
|
664
656
|
# Add code to register this component
|
|
665
657
|
if self.settings.opentelemetry_enabled:
|
|
666
658
|
# Use telemetry instrumentation
|
|
667
|
-
registration =
|
|
659
|
+
registration = (
|
|
660
|
+
f"# Register the {component_type.value} "
|
|
661
|
+
f"'{component.name}' with telemetry"
|
|
662
|
+
)
|
|
668
663
|
entry_func = (
|
|
669
664
|
component.entry_function
|
|
670
665
|
if hasattr(component, "entry_function")
|
|
@@ -672,19 +667,31 @@ class CodeGenerator:
|
|
|
672
667
|
else "export"
|
|
673
668
|
)
|
|
674
669
|
|
|
675
|
-
|
|
676
|
-
|
|
670
|
+
registration += (
|
|
671
|
+
f"\n_wrapped_func = instrument_{component_type.value}("
|
|
672
|
+
f"{full_module_path}.{entry_func}, '{component.name}')"
|
|
673
|
+
)
|
|
677
674
|
|
|
678
675
|
if component_type == ComponentType.TOOL:
|
|
679
|
-
registration +=
|
|
676
|
+
registration += (
|
|
677
|
+
f'\nmcp.add_tool(_wrapped_func, name="{component.name}", '
|
|
678
|
+
f'description="{component.docstring or ""}"'
|
|
679
|
+
)
|
|
680
680
|
# Add annotations if present
|
|
681
681
|
if hasattr(component, "annotations") and component.annotations:
|
|
682
682
|
registration += f", annotations={component.annotations}"
|
|
683
683
|
registration += ")"
|
|
684
684
|
elif component_type == ComponentType.RESOURCE:
|
|
685
|
-
registration +=
|
|
685
|
+
registration += (
|
|
686
|
+
f"\nmcp.add_resource_fn(_wrapped_func, "
|
|
687
|
+
f'uri="{component.uri_template}", name="{component.name}", '
|
|
688
|
+
f'description="{component.docstring or ""}")'
|
|
689
|
+
)
|
|
686
690
|
else: # PROMPT
|
|
687
|
-
registration +=
|
|
691
|
+
registration += (
|
|
692
|
+
f'\nmcp.add_prompt(_wrapped_func, name="{component.name}", '
|
|
693
|
+
f'description="{component.docstring or ""}")'
|
|
694
|
+
)
|
|
688
695
|
else:
|
|
689
696
|
# Standard registration without telemetry
|
|
690
697
|
if component_type == ComponentType.TOOL:
|
|
@@ -795,6 +802,10 @@ class CodeGenerator:
|
|
|
795
802
|
for key, value in auth_components["fastmcp_args"].items():
|
|
796
803
|
mcp_constructor_args.append(f"{key}={value}")
|
|
797
804
|
|
|
805
|
+
# Add stateless HTTP parameter if enabled
|
|
806
|
+
if self.settings.stateless_http:
|
|
807
|
+
mcp_constructor_args.append("stateless_http=True")
|
|
808
|
+
|
|
798
809
|
# Add OpenTelemetry parameters if enabled
|
|
799
810
|
if self.settings.opentelemetry_enabled:
|
|
800
811
|
mcp_constructor_args.append("lifespan=telemetry_lifespan")
|
|
@@ -803,6 +814,18 @@ class CodeGenerator:
|
|
|
803
814
|
server_code_lines.append(mcp_instance_line)
|
|
804
815
|
server_code_lines.append("")
|
|
805
816
|
|
|
817
|
+
# Add early telemetry initialization if enabled (before component registration)
|
|
818
|
+
early_telemetry_init = []
|
|
819
|
+
if self.settings.opentelemetry_enabled:
|
|
820
|
+
early_telemetry_init.extend(
|
|
821
|
+
[
|
|
822
|
+
"# Initialize telemetry early to ensure instrumentation works",
|
|
823
|
+
"from golf.telemetry.instrumentation import init_telemetry",
|
|
824
|
+
f'init_telemetry("{self.settings.name}")',
|
|
825
|
+
"",
|
|
826
|
+
]
|
|
827
|
+
)
|
|
828
|
+
|
|
806
829
|
# Main entry point with transport-specific app initialization
|
|
807
830
|
main_code = [
|
|
808
831
|
'if __name__ == "__main__":',
|
|
@@ -830,71 +853,84 @@ class CodeGenerator:
|
|
|
830
853
|
|
|
831
854
|
# Transport-specific run methods
|
|
832
855
|
if self.settings.transport == "sse":
|
|
833
|
-
# Check if we need
|
|
856
|
+
# Check if we need middleware for SSE
|
|
857
|
+
middleware_setup = []
|
|
858
|
+
middleware_list = []
|
|
859
|
+
|
|
834
860
|
api_key_config = get_api_key_config()
|
|
835
861
|
if auth_components.get("has_auth") and api_key_config:
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
" # For SSE with API key auth, we need to get the app and add middleware",
|
|
839
|
-
' app = mcp.http_app(transport="sse")',
|
|
840
|
-
" app.add_middleware(ApiKeyMiddleware)",
|
|
841
|
-
]
|
|
862
|
+
middleware_setup.append(
|
|
863
|
+
" from starlette.middleware import Middleware"
|
|
842
864
|
)
|
|
843
|
-
|
|
865
|
+
middleware_list.append("Middleware(ApiKeyMiddleware)")
|
|
866
|
+
|
|
867
|
+
# Add OpenTelemetry middleware if enabled
|
|
868
|
+
if self.settings.opentelemetry_enabled:
|
|
869
|
+
middleware_setup.append(
|
|
870
|
+
" from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware"
|
|
871
|
+
)
|
|
872
|
+
middleware_setup.append(
|
|
873
|
+
" from starlette.middleware import Middleware"
|
|
874
|
+
)
|
|
875
|
+
middleware_list.append("Middleware(OpenTelemetryMiddleware)")
|
|
876
|
+
|
|
877
|
+
if middleware_setup:
|
|
878
|
+
main_code.extend(middleware_setup)
|
|
879
|
+
main_code.append(f" middleware = [{', '.join(middleware_list)}]")
|
|
880
|
+
main_code.append("")
|
|
844
881
|
main_code.extend(
|
|
845
882
|
[
|
|
846
|
-
" #
|
|
847
|
-
'
|
|
883
|
+
" # Run SSE server with middleware using FastMCP's run method",
|
|
884
|
+
' mcp.run(transport="sse", host=host, port=port, log_level="info", middleware=middleware)',
|
|
848
885
|
]
|
|
849
886
|
)
|
|
850
|
-
|
|
851
|
-
# Add OpenTelemetry middleware to the SSE app if enabled
|
|
852
|
-
if self.settings.opentelemetry_enabled:
|
|
887
|
+
else:
|
|
853
888
|
main_code.extend(
|
|
854
889
|
[
|
|
855
|
-
" #
|
|
856
|
-
|
|
857
|
-
" app = OpenTelemetryMiddleware(app)",
|
|
890
|
+
" # Run SSE server using FastMCP's run method",
|
|
891
|
+
' mcp.run(transport="sse", host=host, port=port, log_level="info")',
|
|
858
892
|
]
|
|
859
893
|
)
|
|
860
894
|
|
|
861
|
-
main_code.extend(
|
|
862
|
-
[
|
|
863
|
-
" # Run with the configured app",
|
|
864
|
-
' uvicorn.run(app, host=host, port=port, log_level="info")',
|
|
865
|
-
]
|
|
866
|
-
)
|
|
867
895
|
elif self.settings.transport in ["streamable-http", "http"]:
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
" app = mcp.http_app()",
|
|
872
|
-
]
|
|
873
|
-
)
|
|
896
|
+
# Check if we need middleware for streamable-http
|
|
897
|
+
middleware_setup = []
|
|
898
|
+
middleware_list = []
|
|
874
899
|
|
|
875
|
-
# Check if we need to add API key middleware
|
|
876
900
|
api_key_config = get_api_key_config()
|
|
877
901
|
if auth_components.get("has_auth") and api_key_config:
|
|
902
|
+
middleware_setup.append(
|
|
903
|
+
" from starlette.middleware import Middleware"
|
|
904
|
+
)
|
|
905
|
+
middleware_list.append("Middleware(ApiKeyMiddleware)")
|
|
906
|
+
|
|
907
|
+
# Add OpenTelemetry middleware if enabled
|
|
908
|
+
if self.settings.opentelemetry_enabled:
|
|
909
|
+
middleware_setup.append(
|
|
910
|
+
" from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware"
|
|
911
|
+
)
|
|
912
|
+
middleware_setup.append(
|
|
913
|
+
" from starlette.middleware import Middleware"
|
|
914
|
+
)
|
|
915
|
+
middleware_list.append("Middleware(OpenTelemetryMiddleware)")
|
|
916
|
+
|
|
917
|
+
if middleware_setup:
|
|
918
|
+
main_code.extend(middleware_setup)
|
|
919
|
+
main_code.append(f" middleware = [{', '.join(middleware_list)}]")
|
|
920
|
+
main_code.append("")
|
|
878
921
|
main_code.extend(
|
|
879
922
|
[
|
|
880
|
-
" #
|
|
881
|
-
|
|
923
|
+
" # Run HTTP server with middleware using FastMCP's run method",
|
|
924
|
+
' mcp.run(transport="streamable-http", host=host, port=port, log_level="info", middleware=middleware)',
|
|
882
925
|
]
|
|
883
926
|
)
|
|
884
|
-
|
|
885
|
-
# Add OpenTelemetry middleware to the HTTP app if enabled
|
|
886
|
-
if self.settings.opentelemetry_enabled:
|
|
927
|
+
else:
|
|
887
928
|
main_code.extend(
|
|
888
929
|
[
|
|
889
|
-
" #
|
|
890
|
-
|
|
891
|
-
" app = OpenTelemetryMiddleware(app)",
|
|
930
|
+
" # Run HTTP server using FastMCP's run method",
|
|
931
|
+
' mcp.run(transport="streamable-http", host=host, port=port, log_level="info")',
|
|
892
932
|
]
|
|
893
933
|
)
|
|
894
|
-
|
|
895
|
-
main_code.extend(
|
|
896
|
-
[' uvicorn.run(app, host=host, port=port, log_level="info")']
|
|
897
|
-
)
|
|
898
934
|
else:
|
|
899
935
|
# For stdio transport, use mcp.run()
|
|
900
936
|
main_code.extend(
|
|
@@ -917,12 +953,13 @@ class CodeGenerator:
|
|
|
917
953
|
|
|
918
954
|
# Combine all sections
|
|
919
955
|
# Order: imports, env_section, auth_setup, server_code (mcp init),
|
|
920
|
-
#
|
|
956
|
+
# early_telemetry_init, component_registrations, health_check_code, main_code (run block)
|
|
921
957
|
code = "\n".join(
|
|
922
958
|
imports
|
|
923
959
|
+ env_section
|
|
924
960
|
+ auth_setup_code
|
|
925
961
|
+ server_code_lines
|
|
962
|
+
+ early_telemetry_init
|
|
926
963
|
+ component_registrations
|
|
927
964
|
+ health_check_code
|
|
928
965
|
+ main_code
|
|
@@ -955,6 +992,21 @@ def build_project(
|
|
|
955
992
|
build_env: Build environment ('dev' or 'prod')
|
|
956
993
|
copy_env: Whether to copy environment variables to the built app
|
|
957
994
|
"""
|
|
995
|
+
# Load Golf credentials from .env for build operations (platform registration, etc.)
|
|
996
|
+
# This happens regardless of copy_env setting to ensure build process works
|
|
997
|
+
from dotenv import load_dotenv
|
|
998
|
+
|
|
999
|
+
project_env_file = project_path / ".env"
|
|
1000
|
+
if project_env_file.exists():
|
|
1001
|
+
# Load GOLF_* variables for build process
|
|
1002
|
+
load_dotenv(project_env_file, override=False)
|
|
1003
|
+
|
|
1004
|
+
# Only log if we actually found the specific Golf platform credentials
|
|
1005
|
+
has_api_key = "GOLF_API_KEY" in os.environ
|
|
1006
|
+
has_server_id = "GOLF_SERVER_ID" in os.environ
|
|
1007
|
+
if has_api_key and has_server_id:
|
|
1008
|
+
console.print("[dim]Loaded Golf credentials for build operations[/dim]")
|
|
1009
|
+
|
|
958
1010
|
# Execute pre_build.py if it exists
|
|
959
1011
|
pre_build_path = project_path / "pre_build.py"
|
|
960
1012
|
if pre_build_path.exists():
|
|
@@ -1056,9 +1108,6 @@ def build_project(
|
|
|
1056
1108
|
env_vars_to_write["OTEL_TRACES_EXPORTER"] = (
|
|
1057
1109
|
settings.opentelemetry_default_exporter
|
|
1058
1110
|
)
|
|
1059
|
-
console.print(
|
|
1060
|
-
f"[info]Setting OTEL_TRACES_EXPORTER to '{settings.opentelemetry_default_exporter}' from golf.json in built app's .env[/info]"
|
|
1061
|
-
)
|
|
1062
1111
|
|
|
1063
1112
|
# 3. Apply Golf's project name as OTEL_SERVICE_NAME if not already set
|
|
1064
1113
|
# (Ensures service name defaults to project name if not specified in user's .env)
|
|
@@ -1110,6 +1159,36 @@ def build_project(
|
|
|
1110
1159
|
)
|
|
1111
1160
|
generator.generate()
|
|
1112
1161
|
|
|
1162
|
+
# Platform registration (only for prod builds)
|
|
1163
|
+
if build_env == "prod":
|
|
1164
|
+
console.print(
|
|
1165
|
+
"[dim]Registering with Golf platform and updating resources...[/dim]"
|
|
1166
|
+
)
|
|
1167
|
+
import asyncio
|
|
1168
|
+
|
|
1169
|
+
try:
|
|
1170
|
+
from golf.core.platform import register_project_with_platform
|
|
1171
|
+
|
|
1172
|
+
asyncio.run(
|
|
1173
|
+
register_project_with_platform(
|
|
1174
|
+
project_path=project_path,
|
|
1175
|
+
settings=settings,
|
|
1176
|
+
components=generator.components,
|
|
1177
|
+
)
|
|
1178
|
+
)
|
|
1179
|
+
console.print("[green]✓ Platform registration completed[/green]")
|
|
1180
|
+
except ImportError:
|
|
1181
|
+
console.print(
|
|
1182
|
+
"[yellow]Warning: Platform registration module not available[/yellow]"
|
|
1183
|
+
)
|
|
1184
|
+
except Exception as e:
|
|
1185
|
+
console.print(
|
|
1186
|
+
f"[yellow]Warning: Platform registration failed: {e}[/yellow]"
|
|
1187
|
+
)
|
|
1188
|
+
console.print(
|
|
1189
|
+
"[yellow]Tip: Ensure GOLF_API_KEY and GOLF_SERVER_ID are available in your .env file[/yellow]"
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1113
1192
|
# Create a simple README
|
|
1114
1193
|
readme_content = f"""# {settings.name}
|
|
1115
1194
|
|
|
@@ -1130,7 +1209,7 @@ This is a standalone FastMCP server generated by GolfMCP.
|
|
|
1130
1209
|
|
|
1131
1210
|
# Copy pyproject.toml with required dependencies
|
|
1132
1211
|
base_dependencies = [
|
|
1133
|
-
"fastmcp>=2.0.0",
|
|
1212
|
+
"fastmcp>=2.0.0,<2.6.0",
|
|
1134
1213
|
"uvicorn>=0.20.0",
|
|
1135
1214
|
"pydantic>=2.0.0",
|
|
1136
1215
|
"python-dotenv>=1.0.0",
|
|
@@ -109,6 +109,12 @@ class Settings(BaseSettings):
|
|
|
109
109
|
health_check_path: str = Field("/health", description="Health check endpoint path")
|
|
110
110
|
health_check_response: str = Field("OK", description="Health check response text")
|
|
111
111
|
|
|
112
|
+
# HTTP session behaviour
|
|
113
|
+
stateless_http: bool = Field(
|
|
114
|
+
False,
|
|
115
|
+
description="Make Streamable-HTTP transport stateless (new session per request)",
|
|
116
|
+
)
|
|
117
|
+
|
|
112
118
|
|
|
113
119
|
def find_config_path(start_path: Path | None = None) -> Path | None:
|
|
114
120
|
"""Find the golf config file by searching upwards from the given path.
|