golf-mcp 0.1.16__py3-none-any.whl → 0.1.17__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 +139 -60
- golf/core/config.py +6 -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/telemetry/instrumentation.py +26 -48
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.17.dist-info}/METADATA +8 -3
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.17.dist-info}/RECORD +20 -19
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.17.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.17.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.17.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.16.dist-info → golf_mcp-0.1.17.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
|
}
|
|
@@ -29,6 +29,15 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
|
|
|
29
29
|
"""
|
|
30
30
|
global _provider
|
|
31
31
|
|
|
32
|
+
# Check for Golf platform integration first
|
|
33
|
+
golf_api_key = os.environ.get("GOLF_API_KEY")
|
|
34
|
+
if golf_api_key and not os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"):
|
|
35
|
+
# Auto-configure for Golf platform
|
|
36
|
+
os.environ["OTEL_TRACES_EXPORTER"] = "otlp_http"
|
|
37
|
+
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:8000/api/v1/otel"
|
|
38
|
+
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"X-Golf-Key={golf_api_key}"
|
|
39
|
+
print("[INFO] Auto-configured OpenTelemetry for Golf platform ingestion")
|
|
40
|
+
|
|
32
41
|
# Check for required environment variables based on exporter type
|
|
33
42
|
exporter_type = os.environ.get("OTEL_TRACES_EXPORTER", "console").lower()
|
|
34
43
|
|
|
@@ -37,7 +46,8 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
|
|
|
37
46
|
endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
|
|
38
47
|
if not endpoint:
|
|
39
48
|
print(
|
|
40
|
-
"[WARNING] OpenTelemetry tracing is disabled:
|
|
49
|
+
"[WARNING] OpenTelemetry tracing is disabled: "
|
|
50
|
+
"OTEL_EXPORTER_OTLP_ENDPOINT is not set for OTLP HTTP exporter"
|
|
41
51
|
)
|
|
42
52
|
return None
|
|
43
53
|
|
|
@@ -47,6 +57,14 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
|
|
|
47
57
|
"service.version": os.environ.get("SERVICE_VERSION", "1.0.0"),
|
|
48
58
|
"service.instance.id": os.environ.get("SERVICE_INSTANCE_ID", "default"),
|
|
49
59
|
}
|
|
60
|
+
|
|
61
|
+
# Add Golf-specific attributes if available
|
|
62
|
+
if golf_api_key:
|
|
63
|
+
golf_server_id = os.environ.get("GOLF_SERVER_ID")
|
|
64
|
+
if golf_server_id:
|
|
65
|
+
resource_attributes["golf.server.id"] = golf_server_id
|
|
66
|
+
resource_attributes["golf.platform.enabled"] = "true"
|
|
67
|
+
|
|
50
68
|
resource = Resource.create(resource_attributes)
|
|
51
69
|
|
|
52
70
|
# Create provider
|
|
@@ -71,6 +89,10 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
|
|
|
71
89
|
exporter = OTLPSpanExporter(
|
|
72
90
|
endpoint=endpoint, headers=header_dict if header_dict else None
|
|
73
91
|
)
|
|
92
|
+
|
|
93
|
+
# Log successful configuration for Golf platform
|
|
94
|
+
if golf_api_key:
|
|
95
|
+
print(f"[INFO] OpenTelemetry configured for Golf platform: {endpoint}")
|
|
74
96
|
else:
|
|
75
97
|
# Default to console exporter
|
|
76
98
|
exporter = ConsoleSpanExporter(out=sys.stderr)
|
|
@@ -113,21 +135,6 @@ def init_telemetry(service_name: str = "golf-mcp-server") -> TracerProvider | No
|
|
|
113
135
|
traceback.print_exc()
|
|
114
136
|
raise
|
|
115
137
|
|
|
116
|
-
# Create a test span to verify everything is working
|
|
117
|
-
try:
|
|
118
|
-
test_tracer = provider.get_tracer("golf.telemetry.test", "1.0.0")
|
|
119
|
-
with test_tracer.start_as_current_span("startup.test") as span:
|
|
120
|
-
span.set_attribute("test", True)
|
|
121
|
-
span.set_attribute("service.name", service_name)
|
|
122
|
-
span.set_attribute("exporter.type", exporter_type)
|
|
123
|
-
span.set_attribute(
|
|
124
|
-
"endpoint", os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "not set")
|
|
125
|
-
)
|
|
126
|
-
except Exception:
|
|
127
|
-
import traceback
|
|
128
|
-
|
|
129
|
-
traceback.print_exc()
|
|
130
|
-
|
|
131
138
|
return provider
|
|
132
139
|
|
|
133
140
|
|
|
@@ -154,15 +161,8 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
|
|
|
154
161
|
|
|
155
162
|
tracer = get_tracer()
|
|
156
163
|
|
|
157
|
-
# Add debug logging
|
|
158
|
-
print(
|
|
159
|
-
f"[TELEMETRY DEBUG] Instrumenting tool: {tool_name} (function: {func.__name__})"
|
|
160
|
-
)
|
|
161
|
-
|
|
162
164
|
@functools.wraps(func)
|
|
163
165
|
async def async_wrapper(*args, **kwargs):
|
|
164
|
-
print(f"[TELEMETRY DEBUG] Executing async tool: {tool_name}")
|
|
165
|
-
|
|
166
166
|
# Create a more descriptive span name
|
|
167
167
|
span_name = f"mcp.tool.{tool_name}.execute"
|
|
168
168
|
|
|
@@ -247,10 +247,6 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
|
|
|
247
247
|
# Add event for tool execution start
|
|
248
248
|
span.add_event("tool.execution.started", {"tool.name": tool_name})
|
|
249
249
|
|
|
250
|
-
print(
|
|
251
|
-
f"[TELEMETRY DEBUG] Tool span created: {span_name} (span_id: {span.get_span_context().span_id:016x})"
|
|
252
|
-
)
|
|
253
|
-
|
|
254
250
|
try:
|
|
255
251
|
result = await func(*args, **kwargs)
|
|
256
252
|
span.set_status(Status(StatusCode.OK))
|
|
@@ -288,9 +284,6 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
|
|
|
288
284
|
# For any result, record its type
|
|
289
285
|
span.set_attribute("mcp.tool.result.class", type(result).__name__)
|
|
290
286
|
|
|
291
|
-
print(
|
|
292
|
-
f"[TELEMETRY DEBUG] Tool execution completed successfully: {tool_name}"
|
|
293
|
-
)
|
|
294
287
|
return result
|
|
295
288
|
except Exception as e:
|
|
296
289
|
span.record_exception(e)
|
|
@@ -305,13 +298,10 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
|
|
|
305
298
|
"error.message": str(e),
|
|
306
299
|
},
|
|
307
300
|
)
|
|
308
|
-
print(f"[TELEMETRY DEBUG] Tool execution failed: {tool_name} - {e}")
|
|
309
301
|
raise
|
|
310
302
|
|
|
311
303
|
@functools.wraps(func)
|
|
312
304
|
def sync_wrapper(*args, **kwargs):
|
|
313
|
-
print(f"[TELEMETRY DEBUG] Executing sync tool: {tool_name}")
|
|
314
|
-
|
|
315
305
|
# Create a more descriptive span name
|
|
316
306
|
span_name = f"mcp.tool.{tool_name}.execute"
|
|
317
307
|
|
|
@@ -396,10 +386,6 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
|
|
|
396
386
|
# Add event for tool execution start
|
|
397
387
|
span.add_event("tool.execution.started", {"tool.name": tool_name})
|
|
398
388
|
|
|
399
|
-
print(
|
|
400
|
-
f"[TELEMETRY DEBUG] Tool span created: {span_name} (span_id: {span.get_span_context().span_id:016x})"
|
|
401
|
-
)
|
|
402
|
-
|
|
403
389
|
try:
|
|
404
390
|
result = func(*args, **kwargs)
|
|
405
391
|
span.set_status(Status(StatusCode.OK))
|
|
@@ -437,9 +423,6 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
|
|
|
437
423
|
# For any result, record its type
|
|
438
424
|
span.set_attribute("mcp.tool.result.class", type(result).__name__)
|
|
439
425
|
|
|
440
|
-
print(
|
|
441
|
-
f"[TELEMETRY DEBUG] Tool execution completed successfully: {tool_name}"
|
|
442
|
-
)
|
|
443
426
|
return result
|
|
444
427
|
except Exception as e:
|
|
445
428
|
span.record_exception(e)
|
|
@@ -454,7 +437,6 @@ def instrument_tool(func: Callable[..., T], tool_name: str) -> Callable[..., T]:
|
|
|
454
437
|
"error.message": str(e),
|
|
455
438
|
},
|
|
456
439
|
)
|
|
457
|
-
print(f"[TELEMETRY DEBUG] Tool execution failed: {tool_name} - {e}")
|
|
458
440
|
raise
|
|
459
441
|
|
|
460
442
|
# Return appropriate wrapper based on function type
|
|
@@ -1004,16 +986,13 @@ async def telemetry_lifespan(mcp_instance):
|
|
|
1004
986
|
app = getattr(mcp_instance, "app", getattr(mcp_instance, "_app", None))
|
|
1005
987
|
if app and hasattr(app, "add_middleware"):
|
|
1006
988
|
app.add_middleware(SessionTracingMiddleware)
|
|
1007
|
-
print("[TELEMETRY DEBUG] Added SessionTracingMiddleware to FastMCP app")
|
|
1008
989
|
|
|
1009
990
|
# Also try to instrument FastMCP's internal handlers
|
|
1010
991
|
if hasattr(mcp_instance, "_tool_manager") and hasattr(
|
|
1011
992
|
mcp_instance._tool_manager, "tools"
|
|
1012
993
|
):
|
|
1013
|
-
print(
|
|
1014
|
-
f"[TELEMETRY DEBUG] Found {len(mcp_instance._tool_manager.tools)} tools in FastMCP"
|
|
1015
|
-
)
|
|
1016
994
|
# The tools should already be instrumented when they were registered
|
|
995
|
+
pass
|
|
1017
996
|
|
|
1018
997
|
# Try to patch FastMCP's request handling to ensure context propagation
|
|
1019
998
|
if hasattr(mcp_instance, "handle_request"):
|
|
@@ -1026,10 +1005,9 @@ async def telemetry_lifespan(mcp_instance):
|
|
|
1026
1005
|
return await original_handle_request(*args, **kwargs)
|
|
1027
1006
|
|
|
1028
1007
|
mcp_instance.handle_request = traced_handle_request
|
|
1029
|
-
print("[TELEMETRY DEBUG] Patched FastMCP handle_request method")
|
|
1030
1008
|
|
|
1031
|
-
except Exception
|
|
1032
|
-
|
|
1009
|
+
except Exception:
|
|
1010
|
+
# Silently continue if middleware setup fails
|
|
1033
1011
|
import traceback
|
|
1034
1012
|
|
|
1035
1013
|
traceback.print_exc()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: golf-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.17
|
|
4
4
|
Summary: Framework for building MCP servers
|
|
5
5
|
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: typer>=0.15.4
|
|
23
23
|
Requires-Dist: rich>=14.0.0
|
|
24
|
-
Requires-Dist: fastmcp
|
|
24
|
+
Requires-Dist: fastmcp<2.6.0,>=2.0.0
|
|
25
25
|
Requires-Dist: pydantic>=2.11.0
|
|
26
26
|
Requires-Dist: python-dotenv>=1.1.0
|
|
27
27
|
Requires-Dist: black>=24.10.0
|
|
@@ -128,7 +128,7 @@ A Golf project initialized with `golf init` will have a structure similar to thi
|
|
|
128
128
|
|
|
129
129
|
- **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
|
|
130
130
|
- **`tools/`**, **`resources/`**, **`prompts/`**: Contain your Python files, each defining a single component. These directories can also contain nested subdirectories to further organize your components (e.g., `tools/payments/charge.py`). The module docstring of each file serves as the component's description.
|
|
131
|
-
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `
|
|
131
|
+
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit_payments` (filename, followed by reversed parent directories under the main category, joined by underscores).
|
|
132
132
|
- **`common.py`** (not shown, but can be placed in subdirectories like `tools/payments/common.py`): Used to share code (clients, models, etc.) among components in the same subdirectory.
|
|
133
133
|
|
|
134
134
|
## Example: Defining a Tool
|
|
@@ -178,6 +178,10 @@ 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
|
+
// HTTP Transport Configuration (optional)
|
|
182
|
+
"stateless_http": false, // Make streamable-http transport stateless (new session per request)
|
|
183
|
+
// When true, server restarts won't break existing client connections
|
|
184
|
+
|
|
181
185
|
// Health Check Configuration (optional)
|
|
182
186
|
"health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
|
|
183
187
|
"health_check_path": "/health", // HTTP path for health check endpoint
|
|
@@ -198,6 +202,7 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
|
|
|
198
202
|
- `"streamable-http"` provides HTTP streaming for traditional API clients
|
|
199
203
|
- `"stdio"` enables integration with command-line tools and scripts
|
|
200
204
|
- **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
|
|
205
|
+
- **`stateless_http`**: When true, makes the streamable-http transport stateless by creating a new session for each request. This ensures that server restarts don't break existing client connections, making the server truly stateless.
|
|
201
206
|
- **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
|
|
202
207
|
- **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
|
|
203
208
|
- **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
|
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
golf/__init__.py,sha256=
|
|
1
|
+
golf/__init__.py,sha256=BzIjnki8Bz3evNWo6bjGxxpLhy_tN9MRYhtM0MnDiWs,23
|
|
2
2
|
golf/auth/__init__.py,sha256=Rj4yUngJklk6xrDCrxqLTtoDAMzF1HcTvy_l8wREeao,4103
|
|
3
3
|
golf/auth/api_key.py,sha256=LiIraLiH2v7s3yavidaI6BDlAEfK8XnWF15QmaJn9G4,2378
|
|
4
4
|
golf/auth/helpers.py,sha256=ZogdcHM7J2PN6cL6F6OLZ3gyoUR3dwAFDxOJQ2DW_bc,6526
|
|
5
5
|
golf/auth/oauth.py,sha256=-TYcMA4ULWNQacmUvzek2uQVMJpRT3hXC_d5D2k9c44,31156
|
|
6
6
|
golf/auth/provider.py,sha256=3loeYrkNwIRDvyUkf8gbcCRJSiKiVXgE_rMGCSCr5mk,3802
|
|
7
7
|
golf/cli/__init__.py,sha256=R8Y8KdD2C8gDo24fXGq-fdWWNeaq3MYjrbaSB8Hb-Hg,45
|
|
8
|
-
golf/cli/main.py,sha256=
|
|
8
|
+
golf/cli/main.py,sha256=3qexjKNL8vYg-48ATYcwW4-Wv45l3VxntW-mSqDAbEc,13958
|
|
9
9
|
golf/commands/__init__.py,sha256=GKtIEm7EPQWRgot73RPZPWegwN7Zm0bHtUJbR63FNiw,83
|
|
10
10
|
golf/commands/build.py,sha256=jhdxB5EwwCC_8PgqdXLUKuBpnycjh0gft3_7EuTo6ro,2319
|
|
11
|
-
golf/commands/init.py,sha256=
|
|
11
|
+
golf/commands/init.py,sha256=DUAvGqOUapWdF2cgWPscqHRvyOZDiajR0F0Wkn_jm-k,10355
|
|
12
12
|
golf/commands/run.py,sha256=xsiG5LZw4qVt3cRTTfIoWP4Bf4AxNBBJKx0NNfoua40,2884
|
|
13
13
|
golf/core/__init__.py,sha256=4bKeskJ2fPaZqkz2xQScSa3phRLLrmrczwSL632jv-o,52
|
|
14
|
-
golf/core/builder.py,sha256=
|
|
14
|
+
golf/core/builder.py,sha256=9TCtZIUaywxMsC3FIj3VFFgEA7sfrER5WaM4BfYAUS8,55807
|
|
15
15
|
golf/core/builder_auth.py,sha256=6oGw1HsHdtAjfbJKoyakBrFp2v6FgrO1hFuLR9thiY4,13821
|
|
16
16
|
golf/core/builder_telemetry.py,sha256=jobFgRSspLQLuitL4ytk6asSUdTqYcDxGY3sTjkrZd4,2654
|
|
17
|
-
golf/core/config.py,sha256=
|
|
18
|
-
golf/core/parser.py,sha256=
|
|
19
|
-
golf/core/
|
|
17
|
+
golf/core/config.py,sha256=B_GcPDjf2PuOemZNaFMOEuzqtnCchQaJCQkYAuULUgQ,7184
|
|
18
|
+
golf/core/parser.py,sha256=BQRus1O9zmzSmyavwLVfN8BpYFkbrWUDrgQ7yrYfAKw,42457
|
|
19
|
+
golf/core/platform.py,sha256=Z2yEi6ilmQCLC_uAD_oZdVO0WvkL4tsyw7sx0vHhysI,6440
|
|
20
|
+
golf/core/telemetry.py,sha256=CjZ7urbizaRjyFiVBjfGW8V4PmNCG1_quk3FvbVTcjw,15772
|
|
20
21
|
golf/core/transformer.py,sha256=_0nM42M41oM9zw_XxPVVS6MErdBSw2B5lULC7_UFLfU,5287
|
|
21
22
|
golf/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
23
|
golf/examples/api_key/.env,sha256=15dewTdeJEMAIuzQmh1SFc1zEN6PwryWgAc14IV02lY,90
|
|
23
|
-
golf/examples/api_key/.env.example,sha256=
|
|
24
|
-
golf/examples/api_key/README.md,sha256=
|
|
25
|
-
golf/examples/api_key/golf.json,sha256=
|
|
24
|
+
golf/examples/api_key/.env.example,sha256=fvM_r9xLoiJ_41ZbcDc_EQ48uyxwb7zf0gP_bhQroEY,29
|
|
25
|
+
golf/examples/api_key/README.md,sha256=wRcgwYArwiRcjL6GKEOkPVQal7L37WnREa_rwhrIE10,2892
|
|
26
|
+
golf/examples/api_key/golf.json,sha256=V7atsB5T706bBlkZ5iVBPIhAA6_3Ba3dN6NOyVaV06g,214
|
|
26
27
|
golf/examples/api_key/pre_build.py,sha256=-12HGLV70sQcPhgN51zx25uN2o48JeFvTByzF2ayYp4,471
|
|
27
28
|
golf/examples/api_key/tools/issues/create.py,sha256=51X0uGaisUMfMkmJCskArpefpnxZ4fnT4T_f7HhqQdU,2721
|
|
28
29
|
golf/examples/api_key/tools/issues/list.py,sha256=Egl2o1YVSU8Iigu8XO_iK1-Pn1OnFL3j6LOzaG10rTI,2720
|
|
@@ -30,9 +31,9 @@ golf/examples/api_key/tools/repos/list.py,sha256=9JTRLFzA7GwMYAaLjpOD85uXxv0JI4H
|
|
|
30
31
|
golf/examples/api_key/tools/search/code.py,sha256=3quEIzrl0i_DM1fjszTcrzaAqMC7p0TZY99NL5GceKY,2993
|
|
31
32
|
golf/examples/api_key/tools/users/get.py,sha256=VF-hdUkba2_DqKSC_F7vqtbx0EXAK06wUFBEluKqLIA,2470
|
|
32
33
|
golf/examples/basic/.env,sha256=CqdcvPXopWppurJ3bBjT2dODlKUrLv629BHnOy8zBkM,247
|
|
33
|
-
golf/examples/basic/.env.example,sha256=
|
|
34
|
+
golf/examples/basic/.env.example,sha256=fqMyaQ7pc9KHFcT4vChBO4AEGrbW7PVXOfqbrCO6j9Q,157
|
|
34
35
|
golf/examples/basic/README.md,sha256=-mY3R6AAnkXT9FPkALDrJtdf9IyKDvuqjsrLAMTLRYI,2663
|
|
35
|
-
golf/examples/basic/golf.json,sha256=
|
|
36
|
+
golf/examples/basic/golf.json,sha256=8DiRIXmWolrILmbHzEG2tK-ZVwu1W2qBXRLz75XEjAs,166
|
|
36
37
|
golf/examples/basic/pre_build.py,sha256=AG1N_sKd1UUtHPL7sw1v3YGOcZPQvoa9xcL79S9qjGI,1037
|
|
37
38
|
golf/examples/basic/prompts/welcome.py,sha256=Qs_OsXdyPNw_cDZU7cnG4a0ZMzka6LF7vmPfax4cmKM,790
|
|
38
39
|
golf/examples/basic/resources/current_time.py,sha256=hxhV7vGoiOv-DMXVNSVax_jkPoYR3CR9q5PpWYkdliI,1157
|
|
@@ -46,10 +47,10 @@ golf/examples/basic/tools/payments/charge.py,sha256=PIYdFV90hu35H1veLI8ueuYwebzr
|
|
|
46
47
|
golf/examples/basic/tools/payments/common.py,sha256=hfyuQRIjrQfSqGjyY55W6pZSD5jL6O0geCE0DGx0v10,1302
|
|
47
48
|
golf/examples/basic/tools/payments/refund.py,sha256=Qpl4GWvUw-L06oGQMbBzG8pikfCWfBCFcpkRiDOzmyQ,1607
|
|
48
49
|
golf/telemetry/__init__.py,sha256=ESGCg5HKwTCIfID1e_K7EE0bOWkSzMidlLtdqQgBd0w,396
|
|
49
|
-
golf/telemetry/instrumentation.py,sha256=
|
|
50
|
-
golf_mcp-0.1.
|
|
51
|
-
golf_mcp-0.1.
|
|
52
|
-
golf_mcp-0.1.
|
|
53
|
-
golf_mcp-0.1.
|
|
54
|
-
golf_mcp-0.1.
|
|
55
|
-
golf_mcp-0.1.
|
|
50
|
+
golf/telemetry/instrumentation.py,sha256=2B0R918-OLpRdyXMTpHuerfc_4osuSi7Lrh9Y2luXfs,43575
|
|
51
|
+
golf_mcp-0.1.17.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
|
|
52
|
+
golf_mcp-0.1.17.dist-info/METADATA,sha256=-OBF6l8aFo-QYVpnf2c2Uas-8obFK5KYp0EaRvlr_7E,12871
|
|
53
|
+
golf_mcp-0.1.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
54
|
+
golf_mcp-0.1.17.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
|
|
55
|
+
golf_mcp-0.1.17.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
|
|
56
|
+
golf_mcp-0.1.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|