golf-mcp 0.1.13__tar.gz → 0.1.16__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.13/src/golf_mcp.egg-info → golf_mcp-0.1.16}/PKG-INFO +36 -1
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/README.md +35 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/pyproject.toml +2 -2
- golf_mcp-0.1.16/src/golf/__init__.py +1 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/cli/main.py +8 -13
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/run.py +12 -4
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/builder.py +44 -3
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/config.py +7 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/parser.py +55 -1
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/telemetry.py +20 -9
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/golf.json +3 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/golf.json +3 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16/src/golf_mcp.egg-info}/PKG-INFO +36 -1
- golf_mcp-0.1.13/src/golf/__init__.py +0 -1
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/docs.md +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/fast-mcp.md +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/fastmcp-example-1.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/fastmcp-example-2.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/mcp.md +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/oauth-implementation.md +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/oauth.md +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/LICENSE +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/MANIFEST.in +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/setup.cfg +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/__init__.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/api_key.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/helpers.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/oauth.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/provider.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/init.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/builder_auth.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/transformer.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/.env +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/.env.example +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/README.md +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/pre_build.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/issues/create.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/issues/list.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/repos/list.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/search/code.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/users/get.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/.env +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/.env.example +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/pre_build.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/weather/common.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/github_user.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/hello.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/payments/charge.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/payments/common.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/payments/refund.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/telemetry/__init__.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/telemetry/instrumentation.py +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/SOURCES.txt +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/requires.txt +0 -0
- {golf_mcp-0.1.13 → golf_mcp-0.1.16}/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.1.
|
|
3
|
+
Version: 0.1.16
|
|
4
4
|
Summary: Framework for building MCP servers
|
|
5
5
|
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -178,6 +178,11 @@ 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
|
+
// Health Check Configuration (optional)
|
|
182
|
+
"health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
|
|
183
|
+
"health_check_path": "/health", // HTTP path for health check endpoint
|
|
184
|
+
"health_check_response": "OK", // Response text returned by health check
|
|
185
|
+
|
|
181
186
|
// OpenTelemetry Configuration (optional)
|
|
182
187
|
"opentelemetry_enabled": false, // Enable distributed tracing
|
|
183
188
|
"opentelemetry_default_exporter": "console" // Default exporter if OTEL_TRACES_EXPORTER not set
|
|
@@ -193,11 +198,41 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
193
198
|
- `"streamable-http"` provides HTTP streaming for traditional API clients
|
|
194
199
|
- `"stdio"` enables integration with command-line tools and scripts
|
|
195
200
|
- **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
|
|
201
|
+
- **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
|
|
202
|
+
- **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
|
|
203
|
+
- **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
|
|
196
204
|
- **`opentelemetry_enabled`**: When true, enables distributed tracing for debugging and monitoring your MCP server
|
|
197
205
|
- **`opentelemetry_default_exporter`**: Sets the default trace exporter. Can be overridden by the `OTEL_TRACES_EXPORTER` environment variable
|
|
198
206
|
|
|
199
207
|
## Features
|
|
200
208
|
|
|
209
|
+
### 🏥 Health Check Support
|
|
210
|
+
|
|
211
|
+
Golf includes built-in health check endpoint support for production deployments. When enabled, it automatically adds a custom HTTP route that can be used by:
|
|
212
|
+
- Kubernetes readiness and liveness probes
|
|
213
|
+
- Load balancers and reverse proxies
|
|
214
|
+
- Monitoring systems
|
|
215
|
+
- Container orchestration platforms
|
|
216
|
+
|
|
217
|
+
#### Configuration
|
|
218
|
+
|
|
219
|
+
Enable health checks in your `golf.json`:
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"health_check_enabled": true,
|
|
223
|
+
"health_check_path": "/health",
|
|
224
|
+
"health_check_response": "Service is healthy"
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The generated server will include a route like:
|
|
229
|
+
```python
|
|
230
|
+
@mcp.custom_route('/health', methods=["GET"])
|
|
231
|
+
async def health_check(request: Request) -> PlainTextResponse:
|
|
232
|
+
"""Health check endpoint for Kubernetes and load balancers."""
|
|
233
|
+
return PlainTextResponse("Service is healthy")
|
|
234
|
+
```
|
|
235
|
+
|
|
201
236
|
### 🔍 OpenTelemetry Support
|
|
202
237
|
|
|
203
238
|
Golf includes built-in OpenTelemetry instrumentation for distributed tracing. When enabled, it automatically traces:
|
|
@@ -140,6 +140,11 @@ 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
|
+
// Health Check Configuration (optional)
|
|
144
|
+
"health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
|
|
145
|
+
"health_check_path": "/health", // HTTP path for health check endpoint
|
|
146
|
+
"health_check_response": "OK", // Response text returned by health check
|
|
147
|
+
|
|
143
148
|
// OpenTelemetry Configuration (optional)
|
|
144
149
|
"opentelemetry_enabled": false, // Enable distributed tracing
|
|
145
150
|
"opentelemetry_default_exporter": "console" // Default exporter if OTEL_TRACES_EXPORTER not set
|
|
@@ -155,11 +160,41 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
155
160
|
- `"streamable-http"` provides HTTP streaming for traditional API clients
|
|
156
161
|
- `"stdio"` enables integration with command-line tools and scripts
|
|
157
162
|
- **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
|
|
163
|
+
- **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
|
|
164
|
+
- **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
|
|
165
|
+
- **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
|
|
158
166
|
- **`opentelemetry_enabled`**: When true, enables distributed tracing for debugging and monitoring your MCP server
|
|
159
167
|
- **`opentelemetry_default_exporter`**: Sets the default trace exporter. Can be overridden by the `OTEL_TRACES_EXPORTER` environment variable
|
|
160
168
|
|
|
161
169
|
## Features
|
|
162
170
|
|
|
171
|
+
### 🏥 Health Check Support
|
|
172
|
+
|
|
173
|
+
Golf includes built-in health check endpoint support for production deployments. When enabled, it automatically adds a custom HTTP route that can be used by:
|
|
174
|
+
- Kubernetes readiness and liveness probes
|
|
175
|
+
- Load balancers and reverse proxies
|
|
176
|
+
- Monitoring systems
|
|
177
|
+
- Container orchestration platforms
|
|
178
|
+
|
|
179
|
+
#### Configuration
|
|
180
|
+
|
|
181
|
+
Enable health checks in your `golf.json`:
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"health_check_enabled": true,
|
|
185
|
+
"health_check_path": "/health",
|
|
186
|
+
"health_check_response": "Service is healthy"
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The generated server will include a route like:
|
|
191
|
+
```python
|
|
192
|
+
@mcp.custom_route('/health', methods=["GET"])
|
|
193
|
+
async def health_check(request: Request) -> PlainTextResponse:
|
|
194
|
+
"""Health check endpoint for Kubernetes and load balancers."""
|
|
195
|
+
return PlainTextResponse("Service is healthy")
|
|
196
|
+
```
|
|
197
|
+
|
|
163
198
|
### 🔍 OpenTelemetry Support
|
|
164
199
|
|
|
165
200
|
Golf includes built-in OpenTelemetry instrumentation for distributed tracing. When enabled, it automatically traces:
|
|
@@ -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.16"
|
|
8
8
|
description = "Framework for building MCP servers"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Antoni Gmitruk", email = "antoni@golf.dev"}
|
|
@@ -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.16"
|
|
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"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.16"
|
|
@@ -16,7 +16,6 @@ from golf.core.telemetry import (
|
|
|
16
16
|
shutdown,
|
|
17
17
|
track_event,
|
|
18
18
|
track_detailed_error,
|
|
19
|
-
_detect_execution_environment,
|
|
20
19
|
)
|
|
21
20
|
|
|
22
21
|
# Create console for rich output
|
|
@@ -163,7 +162,7 @@ def build_dev(
|
|
|
163
162
|
e,
|
|
164
163
|
context="Development build with environment variables",
|
|
165
164
|
operation="build_dev",
|
|
166
|
-
additional_props={"environment": "dev", "copy_env": True}
|
|
165
|
+
additional_props={"environment": "dev", "copy_env": True},
|
|
167
166
|
)
|
|
168
167
|
raise
|
|
169
168
|
|
|
@@ -215,7 +214,7 @@ def build_prod(
|
|
|
215
214
|
e,
|
|
216
215
|
context="Production build without environment variables",
|
|
217
216
|
operation="build_prod",
|
|
218
|
-
additional_props={"environment": "prod", "copy_env": False}
|
|
217
|
+
additional_props={"environment": "prod", "copy_env": False},
|
|
219
218
|
)
|
|
220
219
|
raise
|
|
221
220
|
|
|
@@ -276,15 +275,13 @@ def run(
|
|
|
276
275
|
|
|
277
276
|
build_project(project_root, settings, dist_dir)
|
|
278
277
|
except Exception as e:
|
|
279
|
-
console.print(
|
|
280
|
-
f"[bold red]Error building project:[/bold red] {str(e)}"
|
|
281
|
-
)
|
|
278
|
+
console.print(f"[bold red]Error building project:[/bold red] {str(e)}")
|
|
282
279
|
track_detailed_error(
|
|
283
280
|
"cli_run_failed",
|
|
284
281
|
e,
|
|
285
282
|
context="Auto-build before running server",
|
|
286
283
|
operation="auto_build_before_run",
|
|
287
|
-
additional_props={"auto_build": True}
|
|
284
|
+
additional_props={"auto_build": True},
|
|
288
285
|
)
|
|
289
286
|
raise
|
|
290
287
|
else:
|
|
@@ -327,11 +324,11 @@ def run(
|
|
|
327
324
|
# 2: General interrupt/graceful shutdown
|
|
328
325
|
shutdown_type = {
|
|
329
326
|
130: "UserInterrupt",
|
|
330
|
-
143: "GracefulShutdown",
|
|
327
|
+
143: "GracefulShutdown",
|
|
331
328
|
137: "ForcedShutdown",
|
|
332
|
-
2: "Interrupt"
|
|
329
|
+
2: "Interrupt",
|
|
333
330
|
}.get(return_code, "GracefulShutdown")
|
|
334
|
-
|
|
331
|
+
|
|
335
332
|
track_event(
|
|
336
333
|
"cli_run_shutdown",
|
|
337
334
|
{
|
|
@@ -351,8 +348,6 @@ def run(
|
|
|
351
348
|
"exit_code": return_code,
|
|
352
349
|
"operation": "server_process_execution",
|
|
353
350
|
"context": "Server process terminated with unexpected exit code",
|
|
354
|
-
# Add execution environment context
|
|
355
|
-
"execution_env": _detect_execution_environment(),
|
|
356
351
|
},
|
|
357
352
|
)
|
|
358
353
|
|
|
@@ -365,7 +360,7 @@ def run(
|
|
|
365
360
|
e,
|
|
366
361
|
context="Server execution or startup failure",
|
|
367
362
|
operation="run_server_execution",
|
|
368
|
-
additional_props={"has_dist_dir": dist_dir.exists() if dist_dir else False}
|
|
363
|
+
additional_props={"has_dist_dir": dist_dir.exists() if dist_dir else False},
|
|
369
364
|
)
|
|
370
365
|
raise
|
|
371
366
|
|
|
@@ -70,13 +70,21 @@ def run_server(
|
|
|
70
70
|
elif process.returncode == 130:
|
|
71
71
|
console.print("[yellow]Server stopped by user interrupt (Ctrl+C)[/yellow]")
|
|
72
72
|
elif process.returncode == 143:
|
|
73
|
-
console.print(
|
|
73
|
+
console.print(
|
|
74
|
+
"[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]"
|
|
75
|
+
)
|
|
74
76
|
elif process.returncode == 137:
|
|
75
|
-
console.print(
|
|
77
|
+
console.print(
|
|
78
|
+
"[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]"
|
|
79
|
+
)
|
|
76
80
|
elif process.returncode in [1, 2]:
|
|
77
|
-
console.print(
|
|
81
|
+
console.print(
|
|
82
|
+
f"[red]Server exited with error code {process.returncode}[/red]"
|
|
83
|
+
)
|
|
78
84
|
else:
|
|
79
|
-
console.print(
|
|
85
|
+
console.print(
|
|
86
|
+
f"[orange]Server exited with code {process.returncode}[/orange]"
|
|
87
|
+
)
|
|
80
88
|
|
|
81
89
|
return process.returncode
|
|
82
90
|
except KeyboardInterrupt:
|
|
@@ -97,6 +97,11 @@ class ManifestBuilder:
|
|
|
97
97
|
if required_fields:
|
|
98
98
|
tool_schema["inputSchema"]["required"] = required_fields
|
|
99
99
|
|
|
100
|
+
# Add tool annotations if present
|
|
101
|
+
if component.annotations:
|
|
102
|
+
# Merge with existing annotations (keeping title)
|
|
103
|
+
tool_schema["annotations"].update(component.annotations)
|
|
104
|
+
|
|
100
105
|
# Add the tool to the manifest
|
|
101
106
|
self.manifest["tools"].append(tool_schema)
|
|
102
107
|
|
|
@@ -565,6 +570,15 @@ class CodeGenerator:
|
|
|
565
570
|
]:
|
|
566
571
|
imports.append("import uvicorn")
|
|
567
572
|
|
|
573
|
+
# Add health check imports if enabled
|
|
574
|
+
if self.settings.health_check_enabled:
|
|
575
|
+
imports.extend(
|
|
576
|
+
[
|
|
577
|
+
"from starlette.requests import Request",
|
|
578
|
+
"from starlette.responses import PlainTextResponse",
|
|
579
|
+
]
|
|
580
|
+
)
|
|
581
|
+
|
|
568
582
|
# Get transport-specific configuration
|
|
569
583
|
transport_config = self._get_transport_config(self.settings.transport)
|
|
570
584
|
endpoint_path = transport_config["endpoint_path"]
|
|
@@ -662,7 +676,11 @@ class CodeGenerator:
|
|
|
662
676
|
registration += f"\n_wrapped_func = instrument_{component_type.value}({full_module_path}.{entry_func}, '{component.name}')"
|
|
663
677
|
|
|
664
678
|
if component_type == ComponentType.TOOL:
|
|
665
|
-
registration += f'\nmcp.add_tool(_wrapped_func, name="{component.name}", description="{component.docstring or ""}"
|
|
679
|
+
registration += f'\nmcp.add_tool(_wrapped_func, name="{component.name}", description="{component.docstring or ""}"'
|
|
680
|
+
# Add annotations if present
|
|
681
|
+
if hasattr(component, "annotations") and component.annotations:
|
|
682
|
+
registration += f", annotations={component.annotations}"
|
|
683
|
+
registration += ")"
|
|
666
684
|
elif component_type == ComponentType.RESOURCE:
|
|
667
685
|
registration += f'\nmcp.add_resource_fn(_wrapped_func, uri="{component.uri_template}", name="{component.name}", description="{component.docstring or ""}")'
|
|
668
686
|
else: # PROMPT
|
|
@@ -689,6 +707,11 @@ class CodeGenerator:
|
|
|
689
707
|
# Escape any quotes in the docstring
|
|
690
708
|
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
691
709
|
registration += f', description="{escaped_docstring}"'
|
|
710
|
+
|
|
711
|
+
# Add annotations if present
|
|
712
|
+
if hasattr(component, "annotations") and component.annotations:
|
|
713
|
+
registration += f", annotations={component.annotations}"
|
|
714
|
+
|
|
692
715
|
registration += ")"
|
|
693
716
|
|
|
694
717
|
elif component_type == ComponentType.RESOURCE:
|
|
@@ -711,6 +734,7 @@ class CodeGenerator:
|
|
|
711
734
|
# Escape any quotes in the docstring
|
|
712
735
|
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
713
736
|
registration += f', description="{escaped_docstring}"'
|
|
737
|
+
|
|
714
738
|
registration += ")"
|
|
715
739
|
|
|
716
740
|
else: # PROMPT
|
|
@@ -735,6 +759,7 @@ class CodeGenerator:
|
|
|
735
759
|
# Escape any quotes in the docstring
|
|
736
760
|
escaped_docstring = component.docstring.replace('"', '\\"')
|
|
737
761
|
registration += f', description="{escaped_docstring}"'
|
|
762
|
+
|
|
738
763
|
registration += ")"
|
|
739
764
|
|
|
740
765
|
component_registrations.append(registration)
|
|
@@ -876,6 +901,20 @@ class CodeGenerator:
|
|
|
876
901
|
[" # Run with stdio transport", ' mcp.run(transport="stdio")']
|
|
877
902
|
)
|
|
878
903
|
|
|
904
|
+
# Add health check route if enabled
|
|
905
|
+
health_check_code = []
|
|
906
|
+
if self.settings.health_check_enabled:
|
|
907
|
+
health_check_code = [
|
|
908
|
+
"# Add health check route",
|
|
909
|
+
"@mcp.custom_route('"
|
|
910
|
+
+ self.settings.health_check_path
|
|
911
|
+
+ '\', methods=["GET"])',
|
|
912
|
+
"async def health_check(request: Request) -> PlainTextResponse:",
|
|
913
|
+
' """Health check endpoint for Kubernetes and load balancers."""',
|
|
914
|
+
f' return PlainTextResponse("{self.settings.health_check_response}")',
|
|
915
|
+
"",
|
|
916
|
+
]
|
|
917
|
+
|
|
879
918
|
# Combine all sections
|
|
880
919
|
# Order: imports, env_section, auth_setup, server_code (mcp init),
|
|
881
920
|
# post_init (API key middleware), component_registrations, main_code (run block)
|
|
@@ -885,6 +924,7 @@ class CodeGenerator:
|
|
|
885
924
|
+ auth_setup_code
|
|
886
925
|
+ server_code_lines
|
|
887
926
|
+ component_registrations
|
|
927
|
+
+ health_check_code
|
|
888
928
|
+ main_code
|
|
889
929
|
)
|
|
890
930
|
|
|
@@ -950,10 +990,11 @@ def build_project(
|
|
|
950
990
|
import traceback
|
|
951
991
|
|
|
952
992
|
console.print(f"[red]{traceback.format_exc()}[/red]")
|
|
953
|
-
|
|
993
|
+
|
|
954
994
|
# Track detailed error for pre_build.py execution failures
|
|
955
995
|
try:
|
|
956
996
|
from golf.core.telemetry import track_detailed_error
|
|
997
|
+
|
|
957
998
|
track_detailed_error(
|
|
958
999
|
"build_pre_build_failed",
|
|
959
1000
|
e,
|
|
@@ -962,7 +1003,7 @@ def build_project(
|
|
|
962
1003
|
additional_props={
|
|
963
1004
|
"file_path": str(pre_build_path.relative_to(project_path)),
|
|
964
1005
|
"build_env": build_env,
|
|
965
|
-
}
|
|
1006
|
+
},
|
|
966
1007
|
)
|
|
967
1008
|
except Exception:
|
|
968
1009
|
# Don't let telemetry errors break the build
|
|
@@ -102,6 +102,13 @@ class Settings(BaseSettings):
|
|
|
102
102
|
"console", description="Default OpenTelemetry exporter type"
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
+
# Health check configuration
|
|
106
|
+
health_check_enabled: bool = Field(
|
|
107
|
+
False, description="Enable health check endpoint"
|
|
108
|
+
)
|
|
109
|
+
health_check_path: str = Field("/health", description="Health check endpoint path")
|
|
110
|
+
health_check_response: str = Field("OK", description="Health check response text")
|
|
111
|
+
|
|
105
112
|
|
|
106
113
|
def find_config_path(start_path: Path | None = None) -> Path | None:
|
|
107
114
|
"""Find the golf config file by searching upwards from the given path.
|
|
@@ -18,6 +18,7 @@ class ComponentType(str, Enum):
|
|
|
18
18
|
TOOL = "tool"
|
|
19
19
|
RESOURCE = "resource"
|
|
20
20
|
PROMPT = "prompt"
|
|
21
|
+
ROUTE = "route"
|
|
21
22
|
UNKNOWN = "unknown"
|
|
22
23
|
|
|
23
24
|
|
|
@@ -36,6 +37,7 @@ class ParsedComponent:
|
|
|
36
37
|
parameters: list[str] | None = None # For resources with URI params
|
|
37
38
|
parent_module: str | None = None # For nested components
|
|
38
39
|
entry_function: str | None = None # Store the name of the function to use
|
|
40
|
+
annotations: dict[str, Any] | None = None # Tool annotations for MCP hints
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
class AstParser:
|
|
@@ -223,10 +225,11 @@ class AstParser:
|
|
|
223
225
|
component.parameters = parameters
|
|
224
226
|
|
|
225
227
|
def _process_tool(self, component: ParsedComponent, tree: ast.Module) -> None:
|
|
226
|
-
"""Process a tool component to extract input/output schemas."""
|
|
228
|
+
"""Process a tool component to extract input/output schemas and annotations."""
|
|
227
229
|
# Look for Input and Output classes in the AST
|
|
228
230
|
input_class = None
|
|
229
231
|
output_class = None
|
|
232
|
+
annotations = None
|
|
230
233
|
|
|
231
234
|
for node in tree.body:
|
|
232
235
|
if isinstance(node, ast.ClassDef):
|
|
@@ -234,6 +237,13 @@ class AstParser:
|
|
|
234
237
|
input_class = node
|
|
235
238
|
elif node.name == "Output":
|
|
236
239
|
output_class = node
|
|
240
|
+
# Look for annotations assignment
|
|
241
|
+
elif isinstance(node, ast.Assign):
|
|
242
|
+
for target in node.targets:
|
|
243
|
+
if isinstance(target, ast.Name) and target.id == "annotations":
|
|
244
|
+
if isinstance(node.value, ast.Dict):
|
|
245
|
+
annotations = self._extract_dict_from_ast(node.value)
|
|
246
|
+
break
|
|
237
247
|
|
|
238
248
|
# Process Input class if found
|
|
239
249
|
if input_class:
|
|
@@ -255,6 +265,10 @@ class AstParser:
|
|
|
255
265
|
)
|
|
256
266
|
break
|
|
257
267
|
|
|
268
|
+
# Store annotations if found
|
|
269
|
+
if annotations:
|
|
270
|
+
component.annotations = annotations
|
|
271
|
+
|
|
258
272
|
def _process_resource(self, component: ParsedComponent, tree: ast.Module) -> None:
|
|
259
273
|
"""Process a resource component to extract URI template."""
|
|
260
274
|
# Look for resource_uri assignment in the AST
|
|
@@ -437,6 +451,46 @@ class AstParser:
|
|
|
437
451
|
# Default to string for unknown types
|
|
438
452
|
return "string"
|
|
439
453
|
|
|
454
|
+
def _extract_dict_from_ast(self, dict_node: ast.Dict) -> dict[str, Any]:
|
|
455
|
+
"""Extract a dictionary from an AST Dict node.
|
|
456
|
+
|
|
457
|
+
This handles simple literal dictionaries with string keys and
|
|
458
|
+
boolean/string/number values.
|
|
459
|
+
"""
|
|
460
|
+
result = {}
|
|
461
|
+
|
|
462
|
+
for key, value in zip(dict_node.keys, dict_node.values, strict=False):
|
|
463
|
+
# Extract the key
|
|
464
|
+
if isinstance(key, ast.Constant) and isinstance(key.value, str):
|
|
465
|
+
key_str = key.value
|
|
466
|
+
elif isinstance(key, ast.Str): # For older Python versions
|
|
467
|
+
key_str = key.s
|
|
468
|
+
else:
|
|
469
|
+
# Skip non-string keys
|
|
470
|
+
continue
|
|
471
|
+
|
|
472
|
+
# Extract the value
|
|
473
|
+
if isinstance(value, ast.Constant):
|
|
474
|
+
# Handles strings, numbers, booleans, None
|
|
475
|
+
result[key_str] = value.value
|
|
476
|
+
elif isinstance(value, ast.Str): # For older Python versions
|
|
477
|
+
result[key_str] = value.s
|
|
478
|
+
elif isinstance(value, ast.Num): # For older Python versions
|
|
479
|
+
result[key_str] = value.n
|
|
480
|
+
elif isinstance(
|
|
481
|
+
value, ast.NameConstant
|
|
482
|
+
): # For older Python versions (True/False/None)
|
|
483
|
+
result[key_str] = value.value
|
|
484
|
+
elif isinstance(value, ast.Name):
|
|
485
|
+
# Handle True/False/None as names
|
|
486
|
+
if value.id in ("True", "False", "None"):
|
|
487
|
+
result[key_str] = {"True": True, "False": False, "None": None}[
|
|
488
|
+
value.id
|
|
489
|
+
]
|
|
490
|
+
# We could add more complex value handling here if needed
|
|
491
|
+
|
|
492
|
+
return result
|
|
493
|
+
|
|
440
494
|
|
|
441
495
|
def parse_project(project_path: Path) -> dict[ComponentType, list[ParsedComponent]]:
|
|
442
496
|
"""Parse a GolfMCP project to extract all components."""
|
|
@@ -366,30 +366,32 @@ def track_detailed_error(
|
|
|
366
366
|
# Get the last few frames (most relevant) and sanitize them
|
|
367
367
|
relevant_frames = tb_lines[-3:] if len(tb_lines) > 3 else tb_lines
|
|
368
368
|
sanitized_trace = []
|
|
369
|
-
|
|
369
|
+
|
|
370
370
|
for frame in relevant_frames:
|
|
371
371
|
# Sanitize file paths in stack trace
|
|
372
372
|
sanitized_frame = _sanitize_error_message(frame.strip())
|
|
373
373
|
# Further sanitize common traceback patterns
|
|
374
374
|
sanitized_frame = sanitized_frame.replace('File "[PATH]', 'File "[PATH]')
|
|
375
375
|
sanitized_trace.append(sanitized_frame)
|
|
376
|
-
|
|
376
|
+
|
|
377
377
|
properties["stack_trace"] = " | ".join(sanitized_trace)
|
|
378
|
-
|
|
378
|
+
|
|
379
379
|
# Add the specific line that caused the error if available
|
|
380
|
-
if hasattr(error,
|
|
380
|
+
if hasattr(error, "__traceback__") and error.__traceback__:
|
|
381
381
|
tb = error.__traceback__
|
|
382
382
|
while tb.tb_next:
|
|
383
383
|
tb = tb.tb_next
|
|
384
384
|
properties["error_line"] = tb.tb_lineno
|
|
385
|
-
|
|
385
|
+
|
|
386
386
|
except Exception:
|
|
387
387
|
# Don't fail if we can't capture stack trace
|
|
388
388
|
pass
|
|
389
389
|
|
|
390
390
|
# Add system context for debugging
|
|
391
391
|
try:
|
|
392
|
-
properties["python_executable"] = _sanitize_error_message(
|
|
392
|
+
properties["python_executable"] = _sanitize_error_message(
|
|
393
|
+
platform.python_implementation()
|
|
394
|
+
)
|
|
393
395
|
properties["platform_detail"] = platform.platform()[:50] # Limit length
|
|
394
396
|
except Exception:
|
|
395
397
|
pass
|
|
@@ -398,9 +400,17 @@ def track_detailed_error(
|
|
|
398
400
|
if additional_props:
|
|
399
401
|
# Only include safe additional properties
|
|
400
402
|
safe_additional_keys = {
|
|
401
|
-
"exit_code",
|
|
402
|
-
"
|
|
403
|
-
"
|
|
403
|
+
"exit_code",
|
|
404
|
+
"shutdown_type",
|
|
405
|
+
"environment",
|
|
406
|
+
"template",
|
|
407
|
+
"build_env",
|
|
408
|
+
"transport",
|
|
409
|
+
"component_count",
|
|
410
|
+
"file_path",
|
|
411
|
+
"component_type",
|
|
412
|
+
"validation_error",
|
|
413
|
+
"config_error",
|
|
404
414
|
}
|
|
405
415
|
for key, value in additional_props.items():
|
|
406
416
|
if key in safe_additional_keys:
|
|
@@ -408,6 +418,7 @@ def track_detailed_error(
|
|
|
408
418
|
|
|
409
419
|
track_event(event_name, properties)
|
|
410
420
|
|
|
421
|
+
|
|
411
422
|
def _sanitize_error_message(message: str) -> str:
|
|
412
423
|
"""Sanitize error messages to remove sensitive information."""
|
|
413
424
|
import re
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: golf-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.16
|
|
4
4
|
Summary: Framework for building MCP servers
|
|
5
5
|
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -178,6 +178,11 @@ 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
|
+
// Health Check Configuration (optional)
|
|
182
|
+
"health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
|
|
183
|
+
"health_check_path": "/health", // HTTP path for health check endpoint
|
|
184
|
+
"health_check_response": "OK", // Response text returned by health check
|
|
185
|
+
|
|
181
186
|
// OpenTelemetry Configuration (optional)
|
|
182
187
|
"opentelemetry_enabled": false, // Enable distributed tracing
|
|
183
188
|
"opentelemetry_default_exporter": "console" // Default exporter if OTEL_TRACES_EXPORTER not set
|
|
@@ -193,11 +198,41 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
193
198
|
- `"streamable-http"` provides HTTP streaming for traditional API clients
|
|
194
199
|
- `"stdio"` enables integration with command-line tools and scripts
|
|
195
200
|
- **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
|
|
201
|
+
- **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
|
|
202
|
+
- **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
|
|
203
|
+
- **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
|
|
196
204
|
- **`opentelemetry_enabled`**: When true, enables distributed tracing for debugging and monitoring your MCP server
|
|
197
205
|
- **`opentelemetry_default_exporter`**: Sets the default trace exporter. Can be overridden by the `OTEL_TRACES_EXPORTER` environment variable
|
|
198
206
|
|
|
199
207
|
## Features
|
|
200
208
|
|
|
209
|
+
### 🏥 Health Check Support
|
|
210
|
+
|
|
211
|
+
Golf includes built-in health check endpoint support for production deployments. When enabled, it automatically adds a custom HTTP route that can be used by:
|
|
212
|
+
- Kubernetes readiness and liveness probes
|
|
213
|
+
- Load balancers and reverse proxies
|
|
214
|
+
- Monitoring systems
|
|
215
|
+
- Container orchestration platforms
|
|
216
|
+
|
|
217
|
+
#### Configuration
|
|
218
|
+
|
|
219
|
+
Enable health checks in your `golf.json`:
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"health_check_enabled": true,
|
|
223
|
+
"health_check_path": "/health",
|
|
224
|
+
"health_check_response": "Service is healthy"
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The generated server will include a route like:
|
|
229
|
+
```python
|
|
230
|
+
@mcp.custom_route('/health', methods=["GET"])
|
|
231
|
+
async def health_check(request: Request) -> PlainTextResponse:
|
|
232
|
+
"""Health check endpoint for Kubernetes and load balancers."""
|
|
233
|
+
return PlainTextResponse("Service is healthy")
|
|
234
|
+
```
|
|
235
|
+
|
|
201
236
|
### 🔍 OpenTelemetry Support
|
|
202
237
|
|
|
203
238
|
Golf includes built-in OpenTelemetry instrumentation for distributed tracing. When enabled, it automatically traces:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|