golf-mcp 0.1.17__tar.gz → 0.1.18__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.17/src/golf_mcp.egg-info → golf_mcp-0.1.18}/PKG-INFO +3 -1
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/pyproject.toml +7 -2
- golf_mcp-0.1.18/src/golf/__init__.py +1 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/builder.py +83 -1
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/builder_auth.py +5 -0
- golf_mcp-0.1.18/src/golf/core/builder_metrics.py +232 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/config.py +6 -0
- golf_mcp-0.1.18/src/golf/metrics/__init__.py +10 -0
- golf_mcp-0.1.18/src/golf/metrics/collector.py +239 -0
- golf_mcp-0.1.18/src/golf/metrics/registry.py +12 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/telemetry/instrumentation.py +153 -98
- {golf_mcp-0.1.17 → golf_mcp-0.1.18/src/golf_mcp.egg-info}/PKG-INFO +3 -1
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/SOURCES.txt +4 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/requires.txt +3 -0
- golf_mcp-0.1.17/src/golf/__init__.py +0 -1
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/.docs/docs.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/.docs/fast-mcp.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/.docs/fastmcp-example-1.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/.docs/fastmcp-example-2.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/.docs/mcp.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/.docs/oauth-implementation.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/.docs/oauth.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/LICENSE +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/MANIFEST.in +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/README.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/setup.cfg +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/auth/__init__.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/auth/api_key.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/auth/helpers.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/auth/oauth.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/auth/provider.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/cli/main.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/commands/init.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/commands/run.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/parser.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/platform.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/telemetry.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/core/transformer.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/.env +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/.env.example +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/README.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/golf.json +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/pre_build.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/issues/create.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/issues/list.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/repos/list.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/search/code.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/users/get.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/.env +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/.env.example +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/golf.json +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/pre_build.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/weather/common.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/github_user.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/hello.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/payments/charge.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/payments/common.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/payments/refund.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf/telemetry/__init__.py +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.1.17 → golf_mcp-0.1.18}/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.18
|
|
4
4
|
Summary: Framework for building MCP servers
|
|
5
5
|
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -34,6 +34,8 @@ Requires-Dist: opentelemetry-sdk>=1.33.1; extra == "telemetry"
|
|
|
34
34
|
Requires-Dist: opentelemetry-instrumentation-asgi>=0.40b0; extra == "telemetry"
|
|
35
35
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=0.40b0; extra == "telemetry"
|
|
36
36
|
Requires-Dist: wrapt>=1.17.0; extra == "telemetry"
|
|
37
|
+
Provides-Extra: metrics
|
|
38
|
+
Requires-Dist: prometheus-client>=0.22.1; extra == "metrics"
|
|
37
39
|
Dynamic: license-file
|
|
38
40
|
|
|
39
41
|
<div align="center">
|
|
@@ -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.18"
|
|
8
8
|
description = "Framework for building MCP servers"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Antoni Gmitruk", email = "antoni@golf.dev"}
|
|
@@ -45,6 +45,9 @@ telemetry = [
|
|
|
45
45
|
"opentelemetry-exporter-otlp-proto-http>=0.40b0",
|
|
46
46
|
"wrapt>=1.17.0"
|
|
47
47
|
]
|
|
48
|
+
metrics = [
|
|
49
|
+
"prometheus-client>=0.22.1"
|
|
50
|
+
]
|
|
48
51
|
|
|
49
52
|
[project.scripts]
|
|
50
53
|
golf = "golf.cli.main:app"
|
|
@@ -64,7 +67,7 @@ golf = ["examples/**/*"]
|
|
|
64
67
|
|
|
65
68
|
[tool.poetry]
|
|
66
69
|
name = "golf-mcp"
|
|
67
|
-
version = "0.1.
|
|
70
|
+
version = "0.1.18"
|
|
68
71
|
description = "Framework for building MCP servers with zero boilerplate"
|
|
69
72
|
authors = ["Antoni Gmitruk <antoni@golf.dev>"]
|
|
70
73
|
license = "Apache-2.0"
|
|
@@ -100,9 +103,11 @@ opentelemetry-sdk = {version = ">=1.33.1", optional = true}
|
|
|
100
103
|
opentelemetry-instrumentation-asgi = {version = ">=0.40b0", optional = true}
|
|
101
104
|
opentelemetry-exporter-otlp-proto-http = {version = ">=0.40b0", optional = true}
|
|
102
105
|
wrapt = {version = ">=1.17.0", optional = true}
|
|
106
|
+
prometheus-client = {version = ">=0.22.1", optional = true}
|
|
103
107
|
|
|
104
108
|
[tool.poetry.extras]
|
|
105
109
|
telemetry = ["opentelemetry-api", "opentelemetry-sdk", "opentelemetry-instrumentation-asgi", "opentelemetry-exporter-otlp-proto-http", "wrapt"]
|
|
110
|
+
metrics = ["prometheus-client"]
|
|
106
111
|
|
|
107
112
|
[tool.poetry.group.dev.dependencies]
|
|
108
113
|
pytest = "^7.4.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.18"
|
|
@@ -562,6 +562,18 @@ class CodeGenerator:
|
|
|
562
562
|
if self.settings.opentelemetry_enabled:
|
|
563
563
|
imports.extend(generate_telemetry_imports())
|
|
564
564
|
|
|
565
|
+
# Add metrics imports if enabled
|
|
566
|
+
if self.settings.metrics_enabled:
|
|
567
|
+
from golf.core.builder_metrics import (
|
|
568
|
+
generate_metrics_imports,
|
|
569
|
+
generate_metrics_instrumentation,
|
|
570
|
+
generate_session_tracking,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
imports.extend(generate_metrics_imports())
|
|
574
|
+
imports.extend(generate_metrics_instrumentation())
|
|
575
|
+
imports.extend(generate_session_tracking())
|
|
576
|
+
|
|
565
577
|
# Add health check imports if enabled
|
|
566
578
|
if self.settings.health_check_enabled:
|
|
567
579
|
imports.extend(
|
|
@@ -672,6 +684,44 @@ class CodeGenerator:
|
|
|
672
684
|
f"{full_module_path}.{entry_func}, '{component.name}')"
|
|
673
685
|
)
|
|
674
686
|
|
|
687
|
+
if component_type == ComponentType.TOOL:
|
|
688
|
+
registration += (
|
|
689
|
+
f'\nmcp.add_tool(_wrapped_func, name="{component.name}", '
|
|
690
|
+
f'description="{component.docstring or ""}"'
|
|
691
|
+
)
|
|
692
|
+
# Add annotations if present
|
|
693
|
+
if hasattr(component, "annotations") and component.annotations:
|
|
694
|
+
registration += f", annotations={component.annotations}"
|
|
695
|
+
registration += ")"
|
|
696
|
+
elif component_type == ComponentType.RESOURCE:
|
|
697
|
+
registration += (
|
|
698
|
+
f"\nmcp.add_resource_fn(_wrapped_func, "
|
|
699
|
+
f'uri="{component.uri_template}", name="{component.name}", '
|
|
700
|
+
f'description="{component.docstring or ""}")'
|
|
701
|
+
)
|
|
702
|
+
else: # PROMPT
|
|
703
|
+
registration += (
|
|
704
|
+
f'\nmcp.add_prompt(_wrapped_func, name="{component.name}", '
|
|
705
|
+
f'description="{component.docstring or ""}")'
|
|
706
|
+
)
|
|
707
|
+
elif self.settings.metrics_enabled:
|
|
708
|
+
# Use metrics instrumentation
|
|
709
|
+
registration = (
|
|
710
|
+
f"# Register the {component_type.value} "
|
|
711
|
+
f"'{component.name}' with metrics"
|
|
712
|
+
)
|
|
713
|
+
entry_func = (
|
|
714
|
+
component.entry_function
|
|
715
|
+
if hasattr(component, "entry_function")
|
|
716
|
+
and component.entry_function
|
|
717
|
+
else "export"
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
registration += (
|
|
721
|
+
f"\n_wrapped_func = instrument_{component_type.value}("
|
|
722
|
+
f"{full_module_path}.{entry_func}, '{component.name}')"
|
|
723
|
+
)
|
|
724
|
+
|
|
675
725
|
if component_type == ComponentType.TOOL:
|
|
676
726
|
registration += (
|
|
677
727
|
f'\nmcp.add_tool(_wrapped_func, name="{component.name}", '
|
|
@@ -826,6 +876,15 @@ class CodeGenerator:
|
|
|
826
876
|
]
|
|
827
877
|
)
|
|
828
878
|
|
|
879
|
+
# Add metrics initialization if enabled
|
|
880
|
+
early_metrics_init = []
|
|
881
|
+
if self.settings.metrics_enabled:
|
|
882
|
+
from golf.core.builder_metrics import generate_metrics_initialization
|
|
883
|
+
|
|
884
|
+
early_metrics_init.extend(
|
|
885
|
+
generate_metrics_initialization(self.settings.name)
|
|
886
|
+
)
|
|
887
|
+
|
|
829
888
|
# Main entry point with transport-specific app initialization
|
|
830
889
|
main_code = [
|
|
831
890
|
'if __name__ == "__main__":',
|
|
@@ -864,6 +923,13 @@ class CodeGenerator:
|
|
|
864
923
|
)
|
|
865
924
|
middleware_list.append("Middleware(ApiKeyMiddleware)")
|
|
866
925
|
|
|
926
|
+
# Add metrics middleware if enabled
|
|
927
|
+
if self.settings.metrics_enabled:
|
|
928
|
+
middleware_setup.append(
|
|
929
|
+
" from starlette.middleware import Middleware"
|
|
930
|
+
)
|
|
931
|
+
middleware_list.append("Middleware(MetricsMiddleware)")
|
|
932
|
+
|
|
867
933
|
# Add OpenTelemetry middleware if enabled
|
|
868
934
|
if self.settings.opentelemetry_enabled:
|
|
869
935
|
middleware_setup.append(
|
|
@@ -904,6 +970,13 @@ class CodeGenerator:
|
|
|
904
970
|
)
|
|
905
971
|
middleware_list.append("Middleware(ApiKeyMiddleware)")
|
|
906
972
|
|
|
973
|
+
# Add metrics middleware if enabled
|
|
974
|
+
if self.settings.metrics_enabled:
|
|
975
|
+
middleware_setup.append(
|
|
976
|
+
" from starlette.middleware import Middleware"
|
|
977
|
+
)
|
|
978
|
+
middleware_list.append("Middleware(MetricsMiddleware)")
|
|
979
|
+
|
|
907
980
|
# Add OpenTelemetry middleware if enabled
|
|
908
981
|
if self.settings.opentelemetry_enabled:
|
|
909
982
|
middleware_setup.append(
|
|
@@ -937,6 +1010,13 @@ class CodeGenerator:
|
|
|
937
1010
|
[" # Run with stdio transport", ' mcp.run(transport="stdio")']
|
|
938
1011
|
)
|
|
939
1012
|
|
|
1013
|
+
# Add metrics route if enabled
|
|
1014
|
+
metrics_route_code = []
|
|
1015
|
+
if self.settings.metrics_enabled:
|
|
1016
|
+
from golf.core.builder_metrics import generate_metrics_route
|
|
1017
|
+
|
|
1018
|
+
metrics_route_code = generate_metrics_route(self.settings.metrics_path)
|
|
1019
|
+
|
|
940
1020
|
# Add health check route if enabled
|
|
941
1021
|
health_check_code = []
|
|
942
1022
|
if self.settings.health_check_enabled:
|
|
@@ -953,14 +1033,16 @@ class CodeGenerator:
|
|
|
953
1033
|
|
|
954
1034
|
# Combine all sections
|
|
955
1035
|
# Order: imports, env_section, auth_setup, server_code (mcp init),
|
|
956
|
-
# early_telemetry_init, component_registrations, health_check_code, main_code (run block)
|
|
1036
|
+
# early_telemetry_init, early_metrics_init, component_registrations, metrics_route_code, health_check_code, main_code (run block)
|
|
957
1037
|
code = "\n".join(
|
|
958
1038
|
imports
|
|
959
1039
|
+ env_section
|
|
960
1040
|
+ auth_setup_code
|
|
961
1041
|
+ server_code_lines
|
|
962
1042
|
+ early_telemetry_init
|
|
1043
|
+
+ early_metrics_init
|
|
963
1044
|
+ component_registrations
|
|
1045
|
+
+ metrics_route_code
|
|
964
1046
|
+ health_check_code
|
|
965
1047
|
+ main_code
|
|
966
1048
|
)
|
|
@@ -187,6 +187,11 @@ def generate_api_key_auth_components(
|
|
|
187
187
|
" # Debug mode from environment",
|
|
188
188
|
" debug = os.environ.get('GOLF_API_KEY_DEBUG', '').lower() == 'true'",
|
|
189
189
|
" ",
|
|
190
|
+
" # Skip auth for monitoring endpoints",
|
|
191
|
+
" path = request.url.path",
|
|
192
|
+
" if path in ['/metrics', '/health']:",
|
|
193
|
+
" return await call_next(request)",
|
|
194
|
+
" ",
|
|
190
195
|
" api_key_config = get_api_key_config()",
|
|
191
196
|
" ",
|
|
192
197
|
" if api_key_config:",
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Metrics integration for the GolfMCP build process.
|
|
2
|
+
|
|
3
|
+
This module provides functions for generating Prometheus metrics initialization
|
|
4
|
+
and collection code for FastMCP servers built with GolfMCP.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_metrics_imports() -> list[str]:
|
|
9
|
+
"""Generate import statements for metrics collection.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
List of import statements for metrics
|
|
13
|
+
"""
|
|
14
|
+
return [
|
|
15
|
+
"# Prometheus metrics imports",
|
|
16
|
+
"from golf.metrics import init_metrics, get_metrics_collector",
|
|
17
|
+
"from prometheus_client import generate_latest, CONTENT_TYPE_LATEST",
|
|
18
|
+
"from starlette.responses import Response",
|
|
19
|
+
"from starlette.middleware.base import BaseHTTPMiddleware",
|
|
20
|
+
"from starlette.requests import Request",
|
|
21
|
+
"import time",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def generate_metrics_initialization(server_name: str) -> list[str]:
|
|
26
|
+
"""Generate metrics initialization code.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
server_name: Name of the MCP server
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of code lines for metrics initialization
|
|
33
|
+
"""
|
|
34
|
+
return [
|
|
35
|
+
"# Initialize metrics collection",
|
|
36
|
+
"init_metrics(enabled=True)",
|
|
37
|
+
"",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def generate_metrics_route(metrics_path: str) -> list[str]:
|
|
42
|
+
"""Generate the metrics endpoint route code.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
metrics_path: Path for the metrics endpoint (e.g., "/metrics")
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of code lines for the metrics route
|
|
49
|
+
"""
|
|
50
|
+
return [
|
|
51
|
+
"# Add metrics endpoint",
|
|
52
|
+
f'@mcp.custom_route("{metrics_path}", methods=["GET"])',
|
|
53
|
+
"async def metrics_endpoint(request):",
|
|
54
|
+
' """Prometheus metrics endpoint for monitoring."""',
|
|
55
|
+
" # Update uptime before returning metrics",
|
|
56
|
+
" update_uptime()",
|
|
57
|
+
" return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)",
|
|
58
|
+
"",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_metrics_dependencies() -> list[str]:
|
|
63
|
+
"""Get list of metrics dependencies to add to pyproject.toml.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of package requirements strings
|
|
67
|
+
"""
|
|
68
|
+
return [
|
|
69
|
+
"prometheus-client>=0.19.0",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def generate_metrics_instrumentation() -> list[str]:
|
|
74
|
+
"""Generate metrics instrumentation wrapper functions.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of code lines for metrics instrumentation
|
|
78
|
+
"""
|
|
79
|
+
return [
|
|
80
|
+
"# Metrics instrumentation wrapper functions",
|
|
81
|
+
"import time",
|
|
82
|
+
"import functools",
|
|
83
|
+
"from typing import Any, Callable",
|
|
84
|
+
"",
|
|
85
|
+
"def instrument_tool(func: Callable, tool_name: str) -> Callable:",
|
|
86
|
+
' """Wrap a tool function with metrics collection."""',
|
|
87
|
+
" @functools.wraps(func)",
|
|
88
|
+
" async def wrapper(*args, **kwargs) -> Any:",
|
|
89
|
+
" collector = get_metrics_collector()",
|
|
90
|
+
" start_time = time.time()",
|
|
91
|
+
" status = 'success'",
|
|
92
|
+
" try:",
|
|
93
|
+
" result = await func(*args, **kwargs)",
|
|
94
|
+
" return result",
|
|
95
|
+
" except Exception as e:",
|
|
96
|
+
" status = 'error'",
|
|
97
|
+
" collector.increment_error('tool', type(e).__name__)",
|
|
98
|
+
" raise",
|
|
99
|
+
" finally:",
|
|
100
|
+
" duration = time.time() - start_time",
|
|
101
|
+
" collector.increment_tool_execution(tool_name, status)",
|
|
102
|
+
" collector.record_tool_duration(tool_name, duration)",
|
|
103
|
+
" return wrapper",
|
|
104
|
+
"",
|
|
105
|
+
"def instrument_resource(func: Callable, resource_name: str) -> Callable:",
|
|
106
|
+
' """Wrap a resource function with metrics collection."""',
|
|
107
|
+
" @functools.wraps(func)",
|
|
108
|
+
" async def wrapper(*args, **kwargs) -> Any:",
|
|
109
|
+
" collector = get_metrics_collector()",
|
|
110
|
+
" try:",
|
|
111
|
+
" result = await func(*args, **kwargs)",
|
|
112
|
+
" # Extract URI from args if available for resource_reads metric",
|
|
113
|
+
" if args and len(args) > 0:",
|
|
114
|
+
" uri = str(args[0]) if args[0] else resource_name",
|
|
115
|
+
" else:",
|
|
116
|
+
" uri = resource_name",
|
|
117
|
+
" collector.increment_resource_read(uri)",
|
|
118
|
+
" return result",
|
|
119
|
+
" except Exception as e:",
|
|
120
|
+
" collector.increment_error('resource', type(e).__name__)",
|
|
121
|
+
" raise",
|
|
122
|
+
" return wrapper",
|
|
123
|
+
"",
|
|
124
|
+
"def instrument_prompt(func: Callable, prompt_name: str) -> Callable:",
|
|
125
|
+
' """Wrap a prompt function with metrics collection."""',
|
|
126
|
+
" @functools.wraps(func)",
|
|
127
|
+
" async def wrapper(*args, **kwargs) -> Any:",
|
|
128
|
+
" collector = get_metrics_collector()",
|
|
129
|
+
" try:",
|
|
130
|
+
" result = await func(*args, **kwargs)",
|
|
131
|
+
" collector.increment_prompt_generation(prompt_name)",
|
|
132
|
+
" return result",
|
|
133
|
+
" except Exception as e:",
|
|
134
|
+
" collector.increment_error('prompt', type(e).__name__)",
|
|
135
|
+
" raise",
|
|
136
|
+
" return wrapper",
|
|
137
|
+
"",
|
|
138
|
+
"# HTTP Request Metrics Middleware",
|
|
139
|
+
"class MetricsMiddleware(BaseHTTPMiddleware):",
|
|
140
|
+
' """Middleware to collect HTTP request metrics."""',
|
|
141
|
+
"",
|
|
142
|
+
" async def dispatch(self, request: Request, call_next):",
|
|
143
|
+
" collector = get_metrics_collector()",
|
|
144
|
+
" start_time = time.time()",
|
|
145
|
+
" ",
|
|
146
|
+
" # Extract path and method",
|
|
147
|
+
" method = request.method",
|
|
148
|
+
" path = request.url.path",
|
|
149
|
+
" ",
|
|
150
|
+
" try:",
|
|
151
|
+
" response = await call_next(request)",
|
|
152
|
+
" status_code = response.status_code",
|
|
153
|
+
" except Exception as e:",
|
|
154
|
+
" status_code = 500",
|
|
155
|
+
" collector.increment_error('http', type(e).__name__)",
|
|
156
|
+
" raise",
|
|
157
|
+
" finally:",
|
|
158
|
+
" duration = time.time() - start_time",
|
|
159
|
+
" collector.increment_http_request(method, status_code, path)",
|
|
160
|
+
" collector.record_http_duration(method, path, duration)",
|
|
161
|
+
" ",
|
|
162
|
+
" return response",
|
|
163
|
+
"",
|
|
164
|
+
"# Session tracking helpers",
|
|
165
|
+
"import atexit",
|
|
166
|
+
"from contextlib import asynccontextmanager",
|
|
167
|
+
"",
|
|
168
|
+
"# Global server start time for uptime tracking",
|
|
169
|
+
"_server_start_time = time.time()",
|
|
170
|
+
"",
|
|
171
|
+
"def track_session_start():",
|
|
172
|
+
' """Track when a new session starts."""',
|
|
173
|
+
" collector = get_metrics_collector()",
|
|
174
|
+
" collector.increment_session()",
|
|
175
|
+
"",
|
|
176
|
+
"def track_session_end(start_time: float):",
|
|
177
|
+
' """Track when a session ends."""',
|
|
178
|
+
" collector = get_metrics_collector()",
|
|
179
|
+
" duration = time.time() - start_time",
|
|
180
|
+
" collector.record_session_duration(duration)",
|
|
181
|
+
"",
|
|
182
|
+
"def update_uptime():",
|
|
183
|
+
' """Update the uptime metric."""',
|
|
184
|
+
" collector = get_metrics_collector()",
|
|
185
|
+
" uptime = time.time() - _server_start_time",
|
|
186
|
+
" collector.set_uptime(uptime)",
|
|
187
|
+
"",
|
|
188
|
+
"# Initialize uptime tracking",
|
|
189
|
+
"update_uptime()",
|
|
190
|
+
"",
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def generate_session_tracking() -> list[str]:
|
|
195
|
+
"""Generate session tracking integration code.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
List of code lines for session tracking
|
|
199
|
+
"""
|
|
200
|
+
return [
|
|
201
|
+
"# Session tracking integration",
|
|
202
|
+
"import asyncio",
|
|
203
|
+
"from typing import Dict",
|
|
204
|
+
"",
|
|
205
|
+
"# Track active sessions",
|
|
206
|
+
"_active_sessions: Dict[str, float] = {}",
|
|
207
|
+
"",
|
|
208
|
+
"# Hook into FastMCP's session lifecycle if available",
|
|
209
|
+
"try:",
|
|
210
|
+
" from fastmcp.server import SessionManager",
|
|
211
|
+
" ",
|
|
212
|
+
" # Monkey patch session creation if possible",
|
|
213
|
+
" _original_create_session = getattr(mcp, '_create_session', None)",
|
|
214
|
+
" if _original_create_session:",
|
|
215
|
+
" async def _patched_create_session(*args, **kwargs):",
|
|
216
|
+
" session_id = str(id(args)) if args else 'unknown'",
|
|
217
|
+
" _active_sessions[session_id] = time.time()",
|
|
218
|
+
" track_session_start()",
|
|
219
|
+
" try:",
|
|
220
|
+
" return await _original_create_session(*args, **kwargs)",
|
|
221
|
+
" except Exception:",
|
|
222
|
+
" # If session creation fails, clean up",
|
|
223
|
+
" if session_id in _active_sessions:",
|
|
224
|
+
" del _active_sessions[session_id]",
|
|
225
|
+
" raise",
|
|
226
|
+
" ",
|
|
227
|
+
" mcp._create_session = _patched_create_session",
|
|
228
|
+
"except (ImportError, AttributeError):",
|
|
229
|
+
" # Fallback: track sessions via request patterns",
|
|
230
|
+
" pass",
|
|
231
|
+
"",
|
|
232
|
+
]
|
|
@@ -115,6 +115,12 @@ class Settings(BaseSettings):
|
|
|
115
115
|
description="Make Streamable-HTTP transport stateless (new session per request)",
|
|
116
116
|
)
|
|
117
117
|
|
|
118
|
+
# Metrics configuration
|
|
119
|
+
metrics_enabled: bool = Field(
|
|
120
|
+
False, description="Enable Prometheus metrics endpoint"
|
|
121
|
+
)
|
|
122
|
+
metrics_path: str = Field("/metrics", description="Metrics endpoint path")
|
|
123
|
+
|
|
118
124
|
|
|
119
125
|
def find_config_path(start_path: Path | None = None) -> Path | None:
|
|
120
126
|
"""Find the golf config file by searching upwards from the given path.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Golf metrics module for Prometheus-compatible metrics collection."""
|
|
2
|
+
|
|
3
|
+
from golf.metrics.collector import MetricsCollector, get_metrics_collector
|
|
4
|
+
from golf.metrics.registry import init_metrics
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"MetricsCollector",
|
|
8
|
+
"get_metrics_collector",
|
|
9
|
+
"init_metrics",
|
|
10
|
+
]
|