golf-mcp 0.2.16__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.
- golf/__init__.py +1 -0
- golf/auth/__init__.py +277 -0
- golf/auth/api_key.py +73 -0
- golf/auth/factory.py +360 -0
- golf/auth/helpers.py +175 -0
- golf/auth/providers.py +586 -0
- golf/auth/registry.py +256 -0
- golf/cli/__init__.py +1 -0
- golf/cli/branding.py +191 -0
- golf/cli/main.py +377 -0
- golf/commands/__init__.py +5 -0
- golf/commands/build.py +81 -0
- golf/commands/init.py +290 -0
- golf/commands/run.py +137 -0
- golf/core/__init__.py +1 -0
- golf/core/builder.py +1884 -0
- golf/core/builder_auth.py +209 -0
- golf/core/builder_metrics.py +221 -0
- golf/core/builder_telemetry.py +99 -0
- golf/core/config.py +199 -0
- golf/core/parser.py +1085 -0
- golf/core/telemetry.py +492 -0
- golf/core/transformer.py +231 -0
- golf/examples/__init__.py +0 -0
- golf/examples/basic/.env.example +4 -0
- golf/examples/basic/README.md +133 -0
- golf/examples/basic/auth.py +76 -0
- golf/examples/basic/golf.json +5 -0
- golf/examples/basic/prompts/welcome.py +27 -0
- golf/examples/basic/resources/current_time.py +34 -0
- golf/examples/basic/resources/info.py +28 -0
- golf/examples/basic/resources/weather/city.py +46 -0
- golf/examples/basic/resources/weather/client.py +48 -0
- golf/examples/basic/resources/weather/current.py +36 -0
- golf/examples/basic/resources/weather/forecast.py +36 -0
- golf/examples/basic/tools/calculator.py +94 -0
- golf/examples/basic/tools/say/hello.py +65 -0
- golf/metrics/__init__.py +10 -0
- golf/metrics/collector.py +320 -0
- golf/metrics/registry.py +12 -0
- golf/telemetry/__init__.py +23 -0
- golf/telemetry/instrumentation.py +1402 -0
- golf/utilities/__init__.py +12 -0
- golf/utilities/context.py +53 -0
- golf/utilities/elicitation.py +170 -0
- golf/utilities/sampling.py +221 -0
- golf_mcp-0.2.16.dist-info/METADATA +262 -0
- golf_mcp-0.2.16.dist-info/RECORD +52 -0
- golf_mcp-0.2.16.dist-info/WHEEL +5 -0
- golf_mcp-0.2.16.dist-info/entry_points.txt +2 -0
- golf_mcp-0.2.16.dist-info/licenses/LICENSE +201 -0
- golf_mcp-0.2.16.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Weather forecast resource example demonstrating nested resources."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .client import weather_client
|
|
7
|
+
|
|
8
|
+
# The URI that clients will use to access this resource
|
|
9
|
+
resource_uri = "weather://forecast"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def forecast_weather() -> dict[str, Any]:
|
|
13
|
+
"""Provide a weather forecast for a default city.
|
|
14
|
+
|
|
15
|
+
This example demonstrates:
|
|
16
|
+
1. Nested resource organization (resources/weather/forecast.py)
|
|
17
|
+
2. Resource without URI parameters
|
|
18
|
+
3. Using shared client from the client.py file
|
|
19
|
+
"""
|
|
20
|
+
# Use the shared weather client from client.py
|
|
21
|
+
forecast_data = await weather_client.get_forecast("New York", days=5)
|
|
22
|
+
|
|
23
|
+
# Add some additional data
|
|
24
|
+
forecast_data.update(
|
|
25
|
+
{
|
|
26
|
+
"updated_at": datetime.now().isoformat(),
|
|
27
|
+
"source": "GolfMCP Weather API",
|
|
28
|
+
"unit": "fahrenheit",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return forecast_data
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Designate the entry point function
|
|
36
|
+
export = forecast_weather
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Enhanced calculator tool with optional LLM-powered explanations."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from golf.utilities import sample
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CalculationResult(BaseModel):
|
|
10
|
+
"""Result of a mathematical calculation."""
|
|
11
|
+
|
|
12
|
+
result: float
|
|
13
|
+
operation: str
|
|
14
|
+
expression: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def calculate(
|
|
18
|
+
expression: Annotated[
|
|
19
|
+
str,
|
|
20
|
+
Field(
|
|
21
|
+
description="Mathematical expression to evaluate (e.g., '2 + 3', '10 * 5', '100 / 4')",
|
|
22
|
+
examples=["2 + 3", "10 * 5.5", "(8 - 3) * 2"],
|
|
23
|
+
),
|
|
24
|
+
],
|
|
25
|
+
explain: Annotated[
|
|
26
|
+
bool,
|
|
27
|
+
Field(
|
|
28
|
+
description="Whether to provide an LLM-powered step-by-step explanation",
|
|
29
|
+
default=False,
|
|
30
|
+
),
|
|
31
|
+
] = False,
|
|
32
|
+
) -> CalculationResult:
|
|
33
|
+
"""Evaluate a mathematical expression with optional LLM explanation.
|
|
34
|
+
|
|
35
|
+
This enhanced calculator can:
|
|
36
|
+
- Perform basic arithmetic operations (+, -, *, /, parentheses)
|
|
37
|
+
- Handle decimal numbers
|
|
38
|
+
- Optionally provide LLM-powered step-by-step explanations
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
- calculate("2 + 3") → 5
|
|
42
|
+
- calculate("10 * 5.5") → 55.0
|
|
43
|
+
- calculate("(8 - 3) * 2", explain=True) → 10 with explanation
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
# Simple expression evaluation using eval (safe for basic math)
|
|
47
|
+
# In production, consider using a proper math expression parser
|
|
48
|
+
allowed_chars = set("0123456789+-*/.() ")
|
|
49
|
+
if not all(c in allowed_chars for c in expression):
|
|
50
|
+
raise ValueError("Expression contains invalid characters")
|
|
51
|
+
|
|
52
|
+
# Evaluate the expression
|
|
53
|
+
result = eval(expression, {"__builtins__": {}}, {})
|
|
54
|
+
|
|
55
|
+
# Ensure result is a number
|
|
56
|
+
if not isinstance(result, (int, float)):
|
|
57
|
+
raise ValueError("Expression did not evaluate to a number")
|
|
58
|
+
|
|
59
|
+
# Generate explanation if requested
|
|
60
|
+
result_expression = expression
|
|
61
|
+
if explain:
|
|
62
|
+
try:
|
|
63
|
+
explanation = await sample(
|
|
64
|
+
f"Explain this mathematical expression step by step: {expression} = {result}",
|
|
65
|
+
system_prompt="You are a helpful math tutor. Provide clear, step-by-step explanations.",
|
|
66
|
+
max_tokens=200,
|
|
67
|
+
)
|
|
68
|
+
result_expression = f"{expression}\n\nExplanation: {explanation}"
|
|
69
|
+
except Exception:
|
|
70
|
+
# If sampling fails, continue without explanation
|
|
71
|
+
result_expression = f"{expression}\n\n(Explanation unavailable)"
|
|
72
|
+
|
|
73
|
+
return CalculationResult(
|
|
74
|
+
result=float(result),
|
|
75
|
+
operation="evaluate",
|
|
76
|
+
expression=result_expression,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
except ZeroDivisionError:
|
|
80
|
+
return CalculationResult(
|
|
81
|
+
result=float("inf"),
|
|
82
|
+
operation="error",
|
|
83
|
+
expression=f"{expression} → Division by zero",
|
|
84
|
+
)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return CalculationResult(
|
|
87
|
+
result=0.0,
|
|
88
|
+
operation="error",
|
|
89
|
+
expression=f"{expression} → Error: {str(e)}",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Export the tool
|
|
94
|
+
export = calculate
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Enhanced hello tool with elicitation capabilities."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from golf.utilities import elicit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Output(BaseModel):
|
|
10
|
+
"""Response from the hello tool."""
|
|
11
|
+
|
|
12
|
+
message: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def hello(
|
|
16
|
+
name: Annotated[str, Field(description="The name of the person to greet")] = "World",
|
|
17
|
+
greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello",
|
|
18
|
+
personalized: Annotated[
|
|
19
|
+
bool,
|
|
20
|
+
Field(
|
|
21
|
+
description="Whether to ask for additional personal details to create a personalized greeting",
|
|
22
|
+
default=False,
|
|
23
|
+
),
|
|
24
|
+
] = False,
|
|
25
|
+
) -> Output:
|
|
26
|
+
"""Say hello with optional personalized elicitation.
|
|
27
|
+
|
|
28
|
+
This enhanced tool can:
|
|
29
|
+
- Provide basic greetings
|
|
30
|
+
- Elicit additional personal information for personalized messages
|
|
31
|
+
- Demonstrate Golf's elicitation capabilities
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
- hello("Alice") → "Hello, Alice!"
|
|
35
|
+
- hello("Bob", personalized=True) → Asks for details, then personalized greeting
|
|
36
|
+
"""
|
|
37
|
+
# Basic greeting
|
|
38
|
+
basic_message = f"{greeting}, {name}!"
|
|
39
|
+
|
|
40
|
+
# If personalized greeting is requested, elicit additional info
|
|
41
|
+
if personalized:
|
|
42
|
+
try:
|
|
43
|
+
# Ask for user's mood
|
|
44
|
+
mood = await elicit(
|
|
45
|
+
"How are you feeling today?",
|
|
46
|
+
["happy", "excited", "calm", "focused", "creative"],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Create personalized message
|
|
50
|
+
personalized_message = f"{greeting}, {name}! Hope you're having a {mood} day!"
|
|
51
|
+
|
|
52
|
+
return Output(message=personalized_message)
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
# If elicitation fails, fall back to basic greeting
|
|
56
|
+
print(f"Personalization failed: {e}")
|
|
57
|
+
return Output(message=f"{basic_message} (personalization unavailable)")
|
|
58
|
+
|
|
59
|
+
# Return basic greeting
|
|
60
|
+
print(f"{greeting} {name}...")
|
|
61
|
+
return Output(message=basic_message)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Designate the entry point function
|
|
65
|
+
export = hello
|
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,320 @@
|
|
|
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
|
+
# Sampling metrics
|
|
70
|
+
self._metrics["sampling_requests"] = Counter(
|
|
71
|
+
"golf_sampling_requests_total",
|
|
72
|
+
"Total number of sampling requests",
|
|
73
|
+
["sampling_type", "status"],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self._metrics["sampling_duration"] = Histogram(
|
|
77
|
+
"golf_sampling_duration_seconds",
|
|
78
|
+
"Sampling request duration in seconds",
|
|
79
|
+
["sampling_type"],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._metrics["sampling_tokens"] = Histogram(
|
|
83
|
+
"golf_sampling_tokens",
|
|
84
|
+
"Number of tokens in sampling responses",
|
|
85
|
+
["sampling_type"],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Elicitation metrics
|
|
89
|
+
self._metrics["elicitation_requests"] = Counter(
|
|
90
|
+
"golf_elicitation_requests_total",
|
|
91
|
+
"Total number of elicitation requests",
|
|
92
|
+
["elicitation_type", "status"],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self._metrics["elicitation_duration"] = Histogram(
|
|
96
|
+
"golf_elicitation_duration_seconds",
|
|
97
|
+
"Elicitation request duration in seconds",
|
|
98
|
+
["elicitation_type"],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Error metrics
|
|
102
|
+
self._metrics["errors"] = Counter(
|
|
103
|
+
"golf_errors_total",
|
|
104
|
+
"Total number of errors",
|
|
105
|
+
["component_type", "error_type"],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Session metrics
|
|
109
|
+
self._metrics["sessions_total"] = Counter("golf_sessions_total", "Total number of sessions created")
|
|
110
|
+
|
|
111
|
+
self._metrics["session_duration"] = Histogram(
|
|
112
|
+
"golf_session_duration_seconds", "Session duration in seconds"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# System metrics
|
|
116
|
+
self._metrics["uptime"] = Gauge("golf_uptime_seconds", "Server uptime in seconds")
|
|
117
|
+
|
|
118
|
+
except ImportError:
|
|
119
|
+
# Prometheus client not available, disable metrics
|
|
120
|
+
self.enabled = False
|
|
121
|
+
|
|
122
|
+
def increment_tool_execution(self, tool_name: str, status: str) -> None:
|
|
123
|
+
"""Record a tool execution.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
tool_name: Name of the tool that was executed
|
|
127
|
+
status: Execution status ('success' or 'error')
|
|
128
|
+
"""
|
|
129
|
+
if not self.enabled or "tool_executions" not in self._metrics:
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
self._metrics["tool_executions"].labels(tool_name=tool_name, status=status).inc()
|
|
133
|
+
|
|
134
|
+
def record_tool_duration(self, tool_name: str, duration: float) -> None:
|
|
135
|
+
"""Record tool execution duration.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
tool_name: Name of the tool
|
|
139
|
+
duration: Execution duration in seconds
|
|
140
|
+
"""
|
|
141
|
+
if not self.enabled or "tool_duration" not in self._metrics:
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
self._metrics["tool_duration"].labels(tool_name=tool_name).observe(duration)
|
|
145
|
+
|
|
146
|
+
def increment_http_request(self, method: str, status_code: int, path: str) -> None:
|
|
147
|
+
"""Record an HTTP request.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
method: HTTP method (GET, POST, etc.)
|
|
151
|
+
status_code: HTTP status code
|
|
152
|
+
path: Request path
|
|
153
|
+
"""
|
|
154
|
+
if not self.enabled or "http_requests" not in self._metrics:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
self._metrics["http_requests"].labels(method=method, status_code=str(status_code), path=path).inc()
|
|
158
|
+
|
|
159
|
+
def record_http_duration(self, method: str, path: str, duration: float) -> None:
|
|
160
|
+
"""Record HTTP request duration.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
method: HTTP method
|
|
164
|
+
path: Request path
|
|
165
|
+
duration: Request duration in seconds
|
|
166
|
+
"""
|
|
167
|
+
if not self.enabled or "http_duration" not in self._metrics:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
self._metrics["http_duration"].labels(method=method, path=path).observe(duration)
|
|
171
|
+
|
|
172
|
+
def increment_resource_read(self, resource_uri: str) -> None:
|
|
173
|
+
"""Record a resource read.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
resource_uri: URI of the resource that was read
|
|
177
|
+
"""
|
|
178
|
+
if not self.enabled or "resource_reads" not in self._metrics:
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
self._metrics["resource_reads"].labels(resource_uri=resource_uri).inc()
|
|
182
|
+
|
|
183
|
+
def increment_prompt_generation(self, prompt_name: str) -> None:
|
|
184
|
+
"""Record a prompt generation.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
prompt_name: Name of the prompt that was generated
|
|
188
|
+
"""
|
|
189
|
+
if not self.enabled or "prompt_generations" not in self._metrics:
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
self._metrics["prompt_generations"].labels(prompt_name=prompt_name).inc()
|
|
193
|
+
|
|
194
|
+
def increment_error(self, component_type: str, error_type: str) -> None:
|
|
195
|
+
"""Record an error.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
component_type: Type of component ('tool', 'resource', 'prompt', 'http')
|
|
199
|
+
error_type: Type of error ('timeout', 'auth_error',
|
|
200
|
+
'validation_error', etc.)
|
|
201
|
+
"""
|
|
202
|
+
if not self.enabled or "errors" not in self._metrics:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
self._metrics["errors"].labels(component_type=component_type, error_type=error_type).inc()
|
|
206
|
+
|
|
207
|
+
def increment_session(self) -> None:
|
|
208
|
+
"""Record a new session."""
|
|
209
|
+
if not self.enabled or "sessions_total" not in self._metrics:
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
self._metrics["sessions_total"].inc()
|
|
213
|
+
|
|
214
|
+
def record_session_duration(self, duration: float) -> None:
|
|
215
|
+
"""Record session duration.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
duration: Session duration in seconds
|
|
219
|
+
"""
|
|
220
|
+
if not self.enabled or "session_duration" not in self._metrics:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
self._metrics["session_duration"].observe(duration)
|
|
224
|
+
|
|
225
|
+
def set_uptime(self, seconds: float) -> None:
|
|
226
|
+
"""Set the server uptime.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
seconds: Server uptime in seconds
|
|
230
|
+
"""
|
|
231
|
+
if not self.enabled or "uptime" not in self._metrics:
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
self._metrics["uptime"].set(seconds)
|
|
235
|
+
|
|
236
|
+
def increment_sampling(self, sampling_type: str, status: str) -> None:
|
|
237
|
+
"""Record a sampling request.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
sampling_type: Type of sampling ('sample', 'structured', 'context')
|
|
241
|
+
status: Request status ('success' or 'error')
|
|
242
|
+
"""
|
|
243
|
+
if not self.enabled or "sampling_requests" not in self._metrics:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
self._metrics["sampling_requests"].labels(sampling_type=sampling_type, status=status).inc()
|
|
247
|
+
|
|
248
|
+
def record_sampling_duration(self, sampling_type: str, duration: float) -> None:
|
|
249
|
+
"""Record sampling request duration.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
sampling_type: Type of sampling
|
|
253
|
+
duration: Request duration in seconds
|
|
254
|
+
"""
|
|
255
|
+
if not self.enabled or "sampling_duration" not in self._metrics:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
self._metrics["sampling_duration"].labels(sampling_type=sampling_type).observe(duration)
|
|
259
|
+
|
|
260
|
+
def record_sampling_tokens(self, sampling_type: str, token_count: int) -> None:
|
|
261
|
+
"""Record sampling token count.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
sampling_type: Type of sampling
|
|
265
|
+
token_count: Number of tokens in the response
|
|
266
|
+
"""
|
|
267
|
+
if not self.enabled or "sampling_tokens" not in self._metrics:
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
self._metrics["sampling_tokens"].labels(sampling_type=sampling_type).observe(token_count)
|
|
271
|
+
|
|
272
|
+
def increment_elicitation(self, elicitation_type: str, status: str) -> None:
|
|
273
|
+
"""Record an elicitation request.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
elicitation_type: Type of elicitation ('elicit', 'confirmation')
|
|
277
|
+
status: Request status ('success' or 'error')
|
|
278
|
+
"""
|
|
279
|
+
if not self.enabled or "elicitation_requests" not in self._metrics:
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
self._metrics["elicitation_requests"].labels(elicitation_type=elicitation_type, status=status).inc()
|
|
283
|
+
|
|
284
|
+
def record_elicitation_duration(self, elicitation_type: str, duration: float) -> None:
|
|
285
|
+
"""Record elicitation request duration.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
elicitation_type: Type of elicitation
|
|
289
|
+
duration: Request duration in seconds
|
|
290
|
+
"""
|
|
291
|
+
if not self.enabled or "elicitation_duration" not in self._metrics:
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
self._metrics["elicitation_duration"].labels(elicitation_type=elicitation_type).observe(duration)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def init_metrics_collector(enabled: bool = False) -> MetricsCollector:
|
|
298
|
+
"""Initialize the global metrics collector.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
enabled: Whether to enable metrics collection
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
The initialized metrics collector
|
|
305
|
+
"""
|
|
306
|
+
global _metrics_collector
|
|
307
|
+
_metrics_collector = MetricsCollector(enabled=enabled)
|
|
308
|
+
return _metrics_collector
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def get_metrics_collector() -> MetricsCollector:
|
|
312
|
+
"""Get the global metrics collector instance.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
The metrics collector, or a disabled one if not initialized
|
|
316
|
+
"""
|
|
317
|
+
global _metrics_collector
|
|
318
|
+
if _metrics_collector is None:
|
|
319
|
+
_metrics_collector = MetricsCollector(enabled=False)
|
|
320
|
+
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)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Golf telemetry module for OpenTelemetry instrumentation."""
|
|
2
|
+
|
|
3
|
+
from golf.telemetry.instrumentation import (
|
|
4
|
+
get_tracer,
|
|
5
|
+
init_telemetry,
|
|
6
|
+
instrument_elicitation,
|
|
7
|
+
instrument_prompt,
|
|
8
|
+
instrument_resource,
|
|
9
|
+
instrument_sampling,
|
|
10
|
+
instrument_tool,
|
|
11
|
+
telemetry_lifespan,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"instrument_tool",
|
|
16
|
+
"instrument_resource",
|
|
17
|
+
"instrument_prompt",
|
|
18
|
+
"instrument_elicitation",
|
|
19
|
+
"instrument_sampling",
|
|
20
|
+
"telemetry_lifespan",
|
|
21
|
+
"init_telemetry",
|
|
22
|
+
"get_tracer",
|
|
23
|
+
]
|