golf-mcp 0.1.16__py3-none-any.whl → 0.1.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of golf-mcp might be problematic. Click here for more details.
- golf/__init__.py +1 -1
- golf/cli/main.py +13 -2
- golf/commands/init.py +63 -1
- golf/core/builder.py +220 -59
- golf/core/builder_auth.py +5 -0
- golf/core/builder_metrics.py +232 -0
- golf/core/config.py +12 -0
- golf/core/parser.py +531 -32
- golf/core/platform.py +180 -0
- golf/core/telemetry.py +28 -8
- golf/examples/api_key/.env.example +1 -5
- golf/examples/api_key/README.md +10 -10
- golf/examples/api_key/golf.json +1 -5
- golf/examples/basic/.env.example +3 -4
- golf/examples/basic/golf.json +1 -5
- golf/metrics/__init__.py +10 -0
- golf/metrics/collector.py +239 -0
- golf/metrics/registry.py +12 -0
- golf/telemetry/instrumentation.py +177 -144
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.18.dist-info}/METADATA +10 -3
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.18.dist-info}/RECORD +25 -20
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.18.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.18.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.18.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.18.dist-info}/top_level.txt +0 -0
golf/core/platform.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Platform registration for Golf MCP projects."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from golf import __version__
|
|
12
|
+
from golf.core.config import Settings
|
|
13
|
+
from golf.core.parser import ComponentType, ParsedComponent
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def register_project_with_platform(
|
|
19
|
+
project_path: Path,
|
|
20
|
+
settings: Settings,
|
|
21
|
+
components: dict[ComponentType, list[ParsedComponent]],
|
|
22
|
+
) -> bool:
|
|
23
|
+
"""Register project with Golf platform during prod build.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
project_path: Path to the project root
|
|
27
|
+
settings: Project settings
|
|
28
|
+
components: Parsed components dictionary
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
True if registration succeeded or was skipped, False if failed
|
|
32
|
+
"""
|
|
33
|
+
# Check if platform integration is enabled
|
|
34
|
+
api_key = os.getenv("GOLF_API_KEY")
|
|
35
|
+
if not api_key:
|
|
36
|
+
return True # Skip silently if no API key
|
|
37
|
+
|
|
38
|
+
# Require explicit server ID
|
|
39
|
+
server_id = os.getenv("GOLF_SERVER_ID")
|
|
40
|
+
if not server_id:
|
|
41
|
+
console.print(
|
|
42
|
+
"[yellow]Warning: Platform registration skipped - GOLF_SERVER_ID environment variable required[/yellow]"
|
|
43
|
+
)
|
|
44
|
+
return True # Skip registration but don't fail build
|
|
45
|
+
|
|
46
|
+
# Build metadata payload
|
|
47
|
+
metadata = {
|
|
48
|
+
"project_name": settings.name,
|
|
49
|
+
"description": settings.description,
|
|
50
|
+
"server_id": server_id,
|
|
51
|
+
"components": _build_component_list(components, project_path),
|
|
52
|
+
"build_timestamp": datetime.utcnow().isoformat(),
|
|
53
|
+
"golf_version": __version__,
|
|
54
|
+
"component_counts": _get_component_counts(components),
|
|
55
|
+
"server_config": {
|
|
56
|
+
"host": settings.host,
|
|
57
|
+
"port": settings.port,
|
|
58
|
+
"transport": settings.transport,
|
|
59
|
+
"auth_enabled": bool(settings.auth),
|
|
60
|
+
"telemetry_enabled": settings.opentelemetry_enabled,
|
|
61
|
+
"health_check_enabled": settings.health_check_enabled,
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
67
|
+
response = await client.post(
|
|
68
|
+
"http://localhost:8000/api/resources",
|
|
69
|
+
json=metadata,
|
|
70
|
+
headers={
|
|
71
|
+
"X-Golf-Key": api_key,
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"User-Agent": f"Golf-MCP/{__version__}",
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
response.raise_for_status()
|
|
77
|
+
|
|
78
|
+
console.print("[green]✓[/green] Registered with Golf platform")
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
except httpx.TimeoutException:
|
|
82
|
+
console.print("[yellow]Warning: Platform registration timed out[/yellow]")
|
|
83
|
+
return False
|
|
84
|
+
except httpx.HTTPStatusError as e:
|
|
85
|
+
if e.response.status_code == 401:
|
|
86
|
+
console.print(
|
|
87
|
+
"[yellow]Warning: Platform registration failed - invalid API key[/yellow]"
|
|
88
|
+
)
|
|
89
|
+
elif e.response.status_code == 403:
|
|
90
|
+
console.print(
|
|
91
|
+
"[yellow]Warning: Platform registration failed - access denied[/yellow]"
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
console.print(
|
|
95
|
+
f"[yellow]Warning: Platform registration failed - HTTP {e.response.status_code}[/yellow]"
|
|
96
|
+
)
|
|
97
|
+
return False
|
|
98
|
+
except Exception as e:
|
|
99
|
+
console.print(f"[yellow]Warning: Platform registration failed: {e}[/yellow]")
|
|
100
|
+
return False # Don't fail the build
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _build_component_list(
|
|
104
|
+
components: dict[ComponentType, list[ParsedComponent]],
|
|
105
|
+
project_path: Path,
|
|
106
|
+
) -> list[dict[str, Any]]:
|
|
107
|
+
"""Convert parsed components to platform format.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
components: Dictionary of parsed components by type
|
|
111
|
+
project_path: Path to the project root
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
List of component metadata dictionaries
|
|
115
|
+
"""
|
|
116
|
+
result = []
|
|
117
|
+
|
|
118
|
+
for comp_type, comp_list in components.items():
|
|
119
|
+
for comp in comp_list:
|
|
120
|
+
# Start with basic component data
|
|
121
|
+
component_data = {
|
|
122
|
+
"name": comp.name,
|
|
123
|
+
"type": comp_type.value,
|
|
124
|
+
"description": comp.docstring,
|
|
125
|
+
"entry_function": comp.entry_function,
|
|
126
|
+
"parent_module": comp.parent_module,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Add file path relative to project root if available
|
|
130
|
+
if comp.file_path:
|
|
131
|
+
try:
|
|
132
|
+
file_path = Path(comp.file_path)
|
|
133
|
+
# Use the provided project_path for relative calculation
|
|
134
|
+
rel_path = file_path.relative_to(project_path)
|
|
135
|
+
component_data["file_path"] = str(rel_path)
|
|
136
|
+
except ValueError:
|
|
137
|
+
# If relative_to fails, try to find a common path or use filename
|
|
138
|
+
component_data["file_path"] = Path(comp.file_path).name
|
|
139
|
+
|
|
140
|
+
# Add schema information only if available and not None
|
|
141
|
+
if hasattr(comp, "input_schema") and comp.input_schema:
|
|
142
|
+
component_data["input_schema"] = comp.input_schema
|
|
143
|
+
if hasattr(comp, "output_schema") and comp.output_schema:
|
|
144
|
+
component_data["output_schema"] = comp.output_schema
|
|
145
|
+
|
|
146
|
+
# Add component-specific fields only if they have values
|
|
147
|
+
if comp_type == ComponentType.RESOURCE:
|
|
148
|
+
if hasattr(comp, "uri_template") and comp.uri_template:
|
|
149
|
+
component_data["uri_template"] = comp.uri_template
|
|
150
|
+
|
|
151
|
+
elif comp_type == ComponentType.TOOL:
|
|
152
|
+
if hasattr(comp, "annotations") and comp.annotations:
|
|
153
|
+
component_data["annotations"] = comp.annotations
|
|
154
|
+
|
|
155
|
+
# Add parameters only if they exist and are not empty
|
|
156
|
+
if hasattr(comp, "parameters") and comp.parameters:
|
|
157
|
+
component_data["parameters"] = comp.parameters
|
|
158
|
+
|
|
159
|
+
result.append(component_data)
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _get_component_counts(
|
|
165
|
+
components: dict[ComponentType, list[ParsedComponent]],
|
|
166
|
+
) -> dict[str, int]:
|
|
167
|
+
"""Get component counts by type.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
components: Dictionary of parsed components by type
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dictionary with counts for each component type
|
|
174
|
+
"""
|
|
175
|
+
return {
|
|
176
|
+
"tools": len(components.get(ComponentType.TOOL, [])),
|
|
177
|
+
"resources": len(components.get(ComponentType.RESOURCE, [])),
|
|
178
|
+
"prompts": len(components.get(ComponentType.PROMPT, [])),
|
|
179
|
+
"total": sum(len(comp_list) for comp_list in components.values()),
|
|
180
|
+
}
|
golf/core/telemetry.py
CHANGED
|
@@ -109,9 +109,9 @@ def is_telemetry_enabled() -> bool:
|
|
|
109
109
|
_telemetry_enabled = saved_preference
|
|
110
110
|
return saved_preference
|
|
111
111
|
|
|
112
|
-
# Default to
|
|
113
|
-
_telemetry_enabled =
|
|
114
|
-
return
|
|
112
|
+
# Default to disabled (opt-in model)
|
|
113
|
+
_telemetry_enabled = False
|
|
114
|
+
return False
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def set_telemetry_enabled(enabled: bool, persist: bool = True) -> None:
|
|
@@ -209,13 +209,24 @@ def initialize_telemetry() -> None:
|
|
|
209
209
|
posthog.disabled = False
|
|
210
210
|
posthog.debug = False
|
|
211
211
|
|
|
212
|
+
# Disable IP collection and GeoIP enrichment at the SDK level
|
|
213
|
+
posthog.set_global_event_properties(
|
|
214
|
+
{
|
|
215
|
+
"$ip": "0", # Override IP with dummy value to prevent collection
|
|
216
|
+
"$geoip_disable": True, # Disable all GeoIP enrichment
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
|
|
212
220
|
except Exception:
|
|
213
221
|
# Telemetry should never break the application
|
|
214
222
|
pass
|
|
215
223
|
|
|
216
224
|
|
|
217
225
|
def track_event(event_name: str, properties: dict[str, Any] | None = None) -> None:
|
|
218
|
-
"""Track an anonymous event with
|
|
226
|
+
"""Track an anonymous event with NO IP address or geolocation data.
|
|
227
|
+
|
|
228
|
+
IP collection and GeoIP enrichment are disabled at the SDK level to ensure
|
|
229
|
+
complete privacy protection. No IP addresses or location data ever reach PostHog.
|
|
219
230
|
|
|
220
231
|
Args:
|
|
221
232
|
event_name: Name of the event (e.g., "cli_init", "cli_build")
|
|
@@ -257,8 +268,16 @@ def track_event(event_name: str, properties: dict[str, Any] | None = None) -> No
|
|
|
257
268
|
}
|
|
258
269
|
}
|
|
259
270
|
|
|
260
|
-
# Identify the user with properties
|
|
261
|
-
posthog.identify(
|
|
271
|
+
# Identify the user with properties (IP tracking disabled)
|
|
272
|
+
posthog.identify(
|
|
273
|
+
distinct_id=anonymous_id,
|
|
274
|
+
properties={
|
|
275
|
+
**person_properties,
|
|
276
|
+
# Explicitly disable IP tracking in identify call
|
|
277
|
+
"$ip": "0",
|
|
278
|
+
"$geoip_disable": True,
|
|
279
|
+
},
|
|
280
|
+
)
|
|
262
281
|
|
|
263
282
|
_user_identified = True
|
|
264
283
|
|
|
@@ -267,8 +286,9 @@ def track_event(event_name: str, properties: dict[str, Any] | None = None) -> No
|
|
|
267
286
|
"golf_version": __version__,
|
|
268
287
|
"python_version": f"{platform.python_version_tuple()[0]}.{platform.python_version_tuple()[1]}",
|
|
269
288
|
"os": platform.system(),
|
|
270
|
-
# Explicitly disable IP tracking
|
|
271
|
-
"$ip":
|
|
289
|
+
# Explicitly disable IP tracking and GeoIP enrichment
|
|
290
|
+
"$ip": "0", # Override IP to prevent collection
|
|
291
|
+
"$geoip_disable": True, # Disable GeoIP enrichment
|
|
272
292
|
}
|
|
273
293
|
|
|
274
294
|
# Filter properties to only include safe ones
|
golf/examples/api_key/README.md
CHANGED
|
@@ -7,26 +7,26 @@ This example demonstrates how to build a GitHub API MCP server using Golf's API
|
|
|
7
7
|
This MCP server provides tools for:
|
|
8
8
|
|
|
9
9
|
- **Repository Management**
|
|
10
|
-
- `
|
|
10
|
+
- `list_repos` - List repositories for users, organizations, or the authenticated user
|
|
11
11
|
|
|
12
12
|
- **Issue Management**
|
|
13
|
-
- `
|
|
14
|
-
- `
|
|
13
|
+
- `create_issues` - Create new issues with labels
|
|
14
|
+
- `list_issues` - List and filter issues by state and labels
|
|
15
15
|
|
|
16
16
|
- **Code Search**
|
|
17
|
-
- `
|
|
17
|
+
- `code_search` - Search for code across GitHub with language and repository filters
|
|
18
18
|
|
|
19
19
|
- **User Information**
|
|
20
|
-
- `
|
|
20
|
+
- `get_users` - Get user profiles or verify authentication
|
|
21
21
|
|
|
22
22
|
## Tool Naming Convention
|
|
23
23
|
|
|
24
24
|
Golf automatically derives tool names from the file structure:
|
|
25
|
-
- `tools/issues/create.py` → `
|
|
26
|
-
- `tools/issues/list.py` → `
|
|
27
|
-
- `tools/repos/list.py` → `
|
|
28
|
-
- `tools/search/code.py` → `
|
|
29
|
-
- `tools/users/get.py` → `
|
|
25
|
+
- `tools/issues/create.py` → `create_issues`
|
|
26
|
+
- `tools/issues/list.py` → `list_issues`
|
|
27
|
+
- `tools/repos/list.py` → `list_repos`
|
|
28
|
+
- `tools/search/code.py` → `code_search`
|
|
29
|
+
- `tools/users/get.py` → `get_users`
|
|
30
30
|
|
|
31
31
|
## Configuration
|
|
32
32
|
|
golf/examples/api_key/golf.json
CHANGED
|
@@ -4,9 +4,5 @@
|
|
|
4
4
|
"host": "127.0.0.1",
|
|
5
5
|
"port": 3000,
|
|
6
6
|
"transport": "sse",
|
|
7
|
-
"
|
|
8
|
-
"health_check_path": "/health",
|
|
9
|
-
"health_check_response": "OK",
|
|
10
|
-
"opentelemetry_enabled": true,
|
|
11
|
-
"opentelemetry_default_exporter": "otlp_http"
|
|
7
|
+
"opentelemetry_enabled": false
|
|
12
8
|
}
|
golf/examples/basic/.env.example
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
GITHUB_CLIENT_ID="default-client-id"
|
|
2
|
+
GITHUB_CLIENT_SECRET="default-secret"
|
|
3
3
|
JWT_SECRET="example-jwt-secret-for-development-only"
|
|
4
|
-
|
|
5
|
-
OTEL_SERVICE_NAME="golf-mcp"
|
|
4
|
+
#GOLF_API_KEY=<your-api-key>
|
golf/examples/basic/golf.json
CHANGED
|
@@ -4,9 +4,5 @@
|
|
|
4
4
|
"host": "127.0.0.1",
|
|
5
5
|
"port": 3000,
|
|
6
6
|
"transport": "sse",
|
|
7
|
-
"
|
|
8
|
-
"health_check_path": "/health",
|
|
9
|
-
"health_check_response": "OK",
|
|
10
|
-
"opentelemetry_enabled": true,
|
|
11
|
-
"opentelemetry_default_exporter": "otlp_http"
|
|
7
|
+
"opentelemetry_enabled": false
|
|
12
8
|
}
|
golf/metrics/__init__.py
ADDED
|
@@ -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
|
+
]
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Metrics collector for Golf MCP servers."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
# Global metrics collector instance
|
|
6
|
+
_metrics_collector: Optional["MetricsCollector"] = None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MetricsCollector:
|
|
10
|
+
"""Collects metrics for Golf MCP servers using Prometheus client."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, enabled: bool = False) -> None:
|
|
13
|
+
"""Initialize the metrics collector.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
enabled: Whether metrics collection is enabled
|
|
17
|
+
"""
|
|
18
|
+
self.enabled = enabled
|
|
19
|
+
self._metrics = {}
|
|
20
|
+
|
|
21
|
+
if self.enabled:
|
|
22
|
+
self._init_prometheus_metrics()
|
|
23
|
+
|
|
24
|
+
def _init_prometheus_metrics(self) -> None:
|
|
25
|
+
"""Initialize Prometheus metrics if enabled."""
|
|
26
|
+
try:
|
|
27
|
+
from prometheus_client import Counter, Histogram, Gauge
|
|
28
|
+
|
|
29
|
+
# Tool execution metrics
|
|
30
|
+
self._metrics["tool_executions"] = Counter(
|
|
31
|
+
"golf_tool_executions_total",
|
|
32
|
+
"Total number of tool executions",
|
|
33
|
+
["tool_name", "status"],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self._metrics["tool_duration"] = Histogram(
|
|
37
|
+
"golf_tool_duration_seconds",
|
|
38
|
+
"Tool execution duration in seconds",
|
|
39
|
+
["tool_name"],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# HTTP request metrics
|
|
43
|
+
self._metrics["http_requests"] = Counter(
|
|
44
|
+
"golf_http_requests_total",
|
|
45
|
+
"Total number of HTTP requests",
|
|
46
|
+
["method", "status_code", "path"],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
self._metrics["http_duration"] = Histogram(
|
|
50
|
+
"golf_http_request_duration_seconds",
|
|
51
|
+
"HTTP request duration in seconds",
|
|
52
|
+
["method", "path"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Resource access metrics
|
|
56
|
+
self._metrics["resource_reads"] = Counter(
|
|
57
|
+
"golf_resource_reads_total",
|
|
58
|
+
"Total number of resource reads",
|
|
59
|
+
["resource_uri"],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Prompt generation metrics
|
|
63
|
+
self._metrics["prompt_generations"] = Counter(
|
|
64
|
+
"golf_prompt_generations_total",
|
|
65
|
+
"Total number of prompt generations",
|
|
66
|
+
["prompt_name"],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Error metrics
|
|
70
|
+
self._metrics["errors"] = Counter(
|
|
71
|
+
"golf_errors_total",
|
|
72
|
+
"Total number of errors",
|
|
73
|
+
["component_type", "error_type"],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Session metrics
|
|
77
|
+
self._metrics["sessions_total"] = Counter(
|
|
78
|
+
"golf_sessions_total", "Total number of sessions created"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self._metrics["session_duration"] = Histogram(
|
|
82
|
+
"golf_session_duration_seconds", "Session duration in seconds"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# System metrics
|
|
86
|
+
self._metrics["uptime"] = Gauge(
|
|
87
|
+
"golf_uptime_seconds", "Server uptime in seconds"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
except ImportError:
|
|
91
|
+
# Prometheus client not available, disable metrics
|
|
92
|
+
self.enabled = False
|
|
93
|
+
|
|
94
|
+
def increment_tool_execution(self, tool_name: str, status: str) -> None:
|
|
95
|
+
"""Record a tool execution.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
tool_name: Name of the tool that was executed
|
|
99
|
+
status: Execution status ('success' or 'error')
|
|
100
|
+
"""
|
|
101
|
+
if not self.enabled or "tool_executions" not in self._metrics:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
self._metrics["tool_executions"].labels(
|
|
105
|
+
tool_name=tool_name, status=status
|
|
106
|
+
).inc()
|
|
107
|
+
|
|
108
|
+
def record_tool_duration(self, tool_name: str, duration: float) -> None:
|
|
109
|
+
"""Record tool execution duration.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
tool_name: Name of the tool
|
|
113
|
+
duration: Execution duration in seconds
|
|
114
|
+
"""
|
|
115
|
+
if not self.enabled or "tool_duration" not in self._metrics:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
self._metrics["tool_duration"].labels(tool_name=tool_name).observe(duration)
|
|
119
|
+
|
|
120
|
+
def increment_http_request(self, method: str, status_code: int, path: str) -> None:
|
|
121
|
+
"""Record an HTTP request.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
method: HTTP method (GET, POST, etc.)
|
|
125
|
+
status_code: HTTP status code
|
|
126
|
+
path: Request path
|
|
127
|
+
"""
|
|
128
|
+
if not self.enabled or "http_requests" not in self._metrics:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
self._metrics["http_requests"].labels(
|
|
132
|
+
method=method, status_code=str(status_code), path=path
|
|
133
|
+
).inc()
|
|
134
|
+
|
|
135
|
+
def record_http_duration(self, method: str, path: str, duration: float) -> None:
|
|
136
|
+
"""Record HTTP request duration.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
method: HTTP method
|
|
140
|
+
path: Request path
|
|
141
|
+
duration: Request duration in seconds
|
|
142
|
+
"""
|
|
143
|
+
if not self.enabled or "http_duration" not in self._metrics:
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
self._metrics["http_duration"].labels(method=method, path=path).observe(
|
|
147
|
+
duration
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def increment_resource_read(self, resource_uri: str) -> None:
|
|
151
|
+
"""Record a resource read.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
resource_uri: URI of the resource that was read
|
|
155
|
+
"""
|
|
156
|
+
if not self.enabled or "resource_reads" not in self._metrics:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
self._metrics["resource_reads"].labels(resource_uri=resource_uri).inc()
|
|
160
|
+
|
|
161
|
+
def increment_prompt_generation(self, prompt_name: str) -> None:
|
|
162
|
+
"""Record a prompt generation.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
prompt_name: Name of the prompt that was generated
|
|
166
|
+
"""
|
|
167
|
+
if not self.enabled or "prompt_generations" not in self._metrics:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
self._metrics["prompt_generations"].labels(prompt_name=prompt_name).inc()
|
|
171
|
+
|
|
172
|
+
def increment_error(self, component_type: str, error_type: str) -> None:
|
|
173
|
+
"""Record an error.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
component_type: Type of component ('tool', 'resource', 'prompt', 'http')
|
|
177
|
+
error_type: Type of error ('timeout', 'auth_error', 'validation_error', etc.)
|
|
178
|
+
"""
|
|
179
|
+
if not self.enabled or "errors" not in self._metrics:
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
self._metrics["errors"].labels(
|
|
183
|
+
component_type=component_type, error_type=error_type
|
|
184
|
+
).inc()
|
|
185
|
+
|
|
186
|
+
def increment_session(self) -> None:
|
|
187
|
+
"""Record a new session."""
|
|
188
|
+
if not self.enabled or "sessions_total" not in self._metrics:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
self._metrics["sessions_total"].inc()
|
|
192
|
+
|
|
193
|
+
def record_session_duration(self, duration: float) -> None:
|
|
194
|
+
"""Record session duration.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
duration: Session duration in seconds
|
|
198
|
+
"""
|
|
199
|
+
if not self.enabled or "session_duration" not in self._metrics:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
self._metrics["session_duration"].observe(duration)
|
|
203
|
+
|
|
204
|
+
def set_uptime(self, seconds: float) -> None:
|
|
205
|
+
"""Set the server uptime.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
seconds: Server uptime in seconds
|
|
209
|
+
"""
|
|
210
|
+
if not self.enabled or "uptime" not in self._metrics:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
self._metrics["uptime"].set(seconds)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def init_metrics_collector(enabled: bool = False) -> MetricsCollector:
|
|
217
|
+
"""Initialize the global metrics collector.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
enabled: Whether to enable metrics collection
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
The initialized metrics collector
|
|
224
|
+
"""
|
|
225
|
+
global _metrics_collector
|
|
226
|
+
_metrics_collector = MetricsCollector(enabled=enabled)
|
|
227
|
+
return _metrics_collector
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def get_metrics_collector() -> MetricsCollector:
|
|
231
|
+
"""Get the global metrics collector instance.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The metrics collector, or a disabled one if not initialized
|
|
235
|
+
"""
|
|
236
|
+
global _metrics_collector
|
|
237
|
+
if _metrics_collector is None:
|
|
238
|
+
_metrics_collector = MetricsCollector(enabled=False)
|
|
239
|
+
return _metrics_collector
|
golf/metrics/registry.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Metrics registry for Golf MCP servers."""
|
|
2
|
+
|
|
3
|
+
from golf.metrics.collector import init_metrics_collector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def init_metrics(enabled: bool = False) -> None:
|
|
7
|
+
"""Initialize the metrics system.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
enabled: Whether to enable metrics collection
|
|
11
|
+
"""
|
|
12
|
+
init_metrics_collector(enabled=enabled)
|