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.

Files changed (69) hide show
  1. {golf_mcp-0.1.13/src/golf_mcp.egg-info → golf_mcp-0.1.16}/PKG-INFO +36 -1
  2. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/README.md +35 -0
  3. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/pyproject.toml +2 -2
  4. golf_mcp-0.1.16/src/golf/__init__.py +1 -0
  5. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/cli/main.py +8 -13
  6. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/run.py +12 -4
  7. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/builder.py +44 -3
  8. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/config.py +7 -0
  9. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/parser.py +55 -1
  10. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/telemetry.py +20 -9
  11. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/golf.json +3 -0
  12. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/golf.json +3 -0
  13. {golf_mcp-0.1.13 → golf_mcp-0.1.16/src/golf_mcp.egg-info}/PKG-INFO +36 -1
  14. golf_mcp-0.1.13/src/golf/__init__.py +0 -1
  15. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/docs.md +0 -0
  16. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/fast-mcp.md +0 -0
  17. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/fastmcp-example-1.py +0 -0
  18. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/fastmcp-example-2.py +0 -0
  19. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/mcp.md +0 -0
  20. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/oauth-implementation.md +0 -0
  21. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/.docs/oauth.md +0 -0
  22. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/LICENSE +0 -0
  23. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/MANIFEST.in +0 -0
  24. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/setup.cfg +0 -0
  25. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/__init__.py +0 -0
  26. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/api_key.py +0 -0
  27. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/helpers.py +0 -0
  28. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/oauth.py +0 -0
  29. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/auth/provider.py +0 -0
  30. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/cli/__init__.py +0 -0
  31. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/__init__.py +0 -0
  32. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/build.py +0 -0
  33. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/commands/init.py +0 -0
  34. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/__init__.py +0 -0
  35. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/builder_auth.py +0 -0
  36. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/builder_telemetry.py +0 -0
  37. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/core/transformer.py +0 -0
  38. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/__init__.py +0 -0
  39. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/.env +0 -0
  40. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/.env.example +0 -0
  41. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/README.md +0 -0
  42. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/pre_build.py +0 -0
  43. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/issues/create.py +0 -0
  44. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/issues/list.py +0 -0
  45. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/repos/list.py +0 -0
  46. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/search/code.py +0 -0
  47. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/api_key/tools/users/get.py +0 -0
  48. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/.env +0 -0
  49. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/.env.example +0 -0
  50. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/README.md +0 -0
  51. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/pre_build.py +0 -0
  52. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/prompts/welcome.py +0 -0
  53. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/current_time.py +0 -0
  54. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/info.py +0 -0
  55. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/weather/common.py +0 -0
  56. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/weather/current.py +0 -0
  57. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
  58. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/github_user.py +0 -0
  59. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/hello.py +0 -0
  60. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/payments/charge.py +0 -0
  61. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/payments/common.py +0 -0
  62. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/examples/basic/tools/payments/refund.py +0 -0
  63. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/telemetry/__init__.py +0 -0
  64. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf/telemetry/instrumentation.py +0 -0
  65. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/SOURCES.txt +0 -0
  66. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  67. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  68. {golf_mcp-0.1.13 → golf_mcp-0.1.16}/src/golf_mcp.egg-info/requires.txt +0 -0
  69. {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.13
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.13"
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.13"
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("[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]")
73
+ console.print(
74
+ "[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]"
75
+ )
74
76
  elif process.returncode == 137:
75
- console.print("[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]")
77
+ console.print(
78
+ "[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]"
79
+ )
76
80
  elif process.returncode in [1, 2]:
77
- console.print(f"[red]Server exited with error code {process.returncode}[/red]")
81
+ console.print(
82
+ f"[red]Server exited with error code {process.returncode}[/red]"
83
+ )
78
84
  else:
79
- console.print(f"[orange]Server exited with code {process.returncode}[/orange]")
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, '__traceback__') and error.__traceback__:
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(platform.python_implementation())
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", "shutdown_type", "environment", "template",
402
- "build_env", "transport", "component_count", "file_path",
403
- "component_type", "validation_error", "config_error"
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
@@ -4,6 +4,9 @@
4
4
  "host": "127.0.0.1",
5
5
  "port": 3000,
6
6
  "transport": "sse",
7
+ "health_check_enabled": false,
8
+ "health_check_path": "/health",
9
+ "health_check_response": "OK",
7
10
  "opentelemetry_enabled": true,
8
11
  "opentelemetry_default_exporter": "otlp_http"
9
12
  }
@@ -4,6 +4,9 @@
4
4
  "host": "127.0.0.1",
5
5
  "port": 3000,
6
6
  "transport": "sse",
7
+ "health_check_enabled": false,
8
+ "health_check_path": "/health",
9
+ "health_check_response": "OK",
7
10
  "opentelemetry_enabled": true,
8
11
  "opentelemetry_default_exporter": "otlp_http"
9
12
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.1.13
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