golf-mcp 0.1.9__tar.gz → 0.1.11__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.9/src/golf_mcp.egg-info → golf_mcp-0.1.11}/PKG-INFO +1 -1
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/pyproject.toml +2 -2
- golf_mcp-0.1.11/src/golf/__init__.py +1 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/auth/__init__.py +1 -1
- golf_mcp-0.1.11/src/golf/auth/helpers.py +207 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/cli/main.py +27 -14
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/commands/init.py +73 -62
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/builder.py +35 -12
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/builder_auth.py +31 -91
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/telemetry.py +57 -4
- golf_mcp-0.1.11/src/golf/telemetry/instrumentation.py +900 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11/src/golf_mcp.egg-info}/PKG-INFO +1 -1
- golf_mcp-0.1.9/src/golf/__init__.py +0 -1
- golf_mcp-0.1.9/src/golf/auth/helpers.py +0 -97
- golf_mcp-0.1.9/src/golf/telemetry/instrumentation.py +0 -540
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/.docs/docs.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/.docs/fast-mcp.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/.docs/fastmcp-example-1.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/.docs/fastmcp-example-2.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/.docs/mcp.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/.docs/oauth-implementation.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/.docs/oauth.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/LICENSE +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/MANIFEST.in +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/README.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/setup.cfg +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/auth/api_key.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/auth/oauth.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/auth/provider.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/commands/run.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/config.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/parser.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/core/transformer.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/.env +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/.env.example +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/README.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/golf.json +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/pre_build.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/issues/create.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/issues/list.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/repos/list.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/search/code.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/users/get.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/.env +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/.env.example +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/golf.json +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/pre_build.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/weather/common.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/github_user.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/hello.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/payments/charge.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/payments/common.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/payments/refund.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf/telemetry/__init__.py +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/SOURCES.txt +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/requires.txt +0 -0
- {golf_mcp-0.1.9 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/top_level.txt +0 -0
|
@@ -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.11"
|
|
8
8
|
description = "Framework for building MCP servers"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Antoni Gmitruk", email = "antoni@golf.dev"}
|
|
@@ -64,7 +64,7 @@ golf = ["examples/**/*"]
|
|
|
64
64
|
|
|
65
65
|
[tool.poetry]
|
|
66
66
|
name = "golf-mcp"
|
|
67
|
-
version = "0.1.
|
|
67
|
+
version = "0.1.11"
|
|
68
68
|
description = "Framework for building MCP servers with zero boilerplate"
|
|
69
69
|
authors = ["Antoni Gmitruk <antoni@golf.dev>"]
|
|
70
70
|
license = "Apache-2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.11"
|
|
@@ -11,7 +11,7 @@ from mcp.server.auth.settings import AuthSettings, ClientRegistrationOptions
|
|
|
11
11
|
|
|
12
12
|
from .provider import ProviderConfig
|
|
13
13
|
from .oauth import GolfOAuthProvider, create_callback_handler
|
|
14
|
-
from .helpers import get_access_token, get_provider_token, extract_token_from_header, get_api_key, set_api_key
|
|
14
|
+
from .helpers import get_access_token, get_provider_token, extract_token_from_header, get_api_key, set_api_key, debug_api_key_context
|
|
15
15
|
from .api_key import configure_api_key, get_api_key_config, is_api_key_configured
|
|
16
16
|
|
|
17
17
|
class AuthConfig:
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Helper functions for working with authentication in MCP context."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
from contextvars import ContextVar
|
|
5
|
+
|
|
6
|
+
# Re-export get_access_token from the MCP SDK
|
|
7
|
+
from mcp.server.auth.middleware.auth_context import get_access_token
|
|
8
|
+
|
|
9
|
+
from .oauth import GolfOAuthProvider
|
|
10
|
+
|
|
11
|
+
# Context variable to store the active OAuth provider
|
|
12
|
+
_active_golf_oauth_provider: Optional[GolfOAuthProvider] = None
|
|
13
|
+
|
|
14
|
+
# Context variable to store the current request's API key
|
|
15
|
+
_current_api_key: ContextVar[Optional[str]] = ContextVar('current_api_key', default=None)
|
|
16
|
+
|
|
17
|
+
def _set_active_golf_oauth_provider(provider: GolfOAuthProvider) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Sets the active GolfOAuthProvider instance.
|
|
20
|
+
Should only be called once during server startup.
|
|
21
|
+
"""
|
|
22
|
+
global _active_golf_oauth_provider
|
|
23
|
+
_active_golf_oauth_provider = provider
|
|
24
|
+
|
|
25
|
+
def get_provider_token() -> Optional[str]:
|
|
26
|
+
"""
|
|
27
|
+
Get a provider token (e.g., GitHub token) associated with the current
|
|
28
|
+
MCP session's access token.
|
|
29
|
+
|
|
30
|
+
This relies on _set_active_golf_oauth_provider being called at server startup.
|
|
31
|
+
"""
|
|
32
|
+
mcp_access_token = get_access_token() # From MCP SDK, uses its own ContextVar
|
|
33
|
+
if not mcp_access_token:
|
|
34
|
+
# No active MCP session token.
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
provider = _active_golf_oauth_provider
|
|
38
|
+
if not provider:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
if not hasattr(provider, "get_provider_token"):
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
# Call the get_provider_token method on the actual GolfOAuthProvider instance
|
|
45
|
+
return provider.get_provider_token(mcp_access_token.token)
|
|
46
|
+
|
|
47
|
+
def extract_token_from_header(auth_header: str) -> Optional[str]:
|
|
48
|
+
"""Extract bearer token from Authorization header.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
auth_header: Authorization header value
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Bearer token or None if not present/valid
|
|
55
|
+
"""
|
|
56
|
+
if not auth_header:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
parts = auth_header.split()
|
|
60
|
+
if len(parts) != 2 or parts[0].lower() != 'bearer':
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return parts[1]
|
|
64
|
+
|
|
65
|
+
def set_api_key(api_key: Optional[str]) -> None:
|
|
66
|
+
"""Set the API key for the current request context.
|
|
67
|
+
|
|
68
|
+
This is an internal function used by the middleware.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
api_key: The API key to store in the context
|
|
72
|
+
"""
|
|
73
|
+
_current_api_key.set(api_key)
|
|
74
|
+
|
|
75
|
+
def get_api_key() -> Optional[str]:
|
|
76
|
+
"""Get the API key from the current request context.
|
|
77
|
+
|
|
78
|
+
This function should be used in tools to retrieve the API key
|
|
79
|
+
that was sent in the request headers.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The API key if available, None otherwise
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
# In a tool file
|
|
86
|
+
from golf.auth import get_api_key
|
|
87
|
+
|
|
88
|
+
async def call_api():
|
|
89
|
+
api_key = get_api_key()
|
|
90
|
+
if not api_key:
|
|
91
|
+
return {"error": "No API key provided"}
|
|
92
|
+
|
|
93
|
+
# Use the API key in your request
|
|
94
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
95
|
+
...
|
|
96
|
+
"""
|
|
97
|
+
# Try to get directly from HTTP request if available (FastMCP pattern)
|
|
98
|
+
try:
|
|
99
|
+
# This follows the FastMCP pattern for accessing HTTP requests
|
|
100
|
+
from fastmcp.server.dependencies import get_http_request
|
|
101
|
+
request = get_http_request()
|
|
102
|
+
|
|
103
|
+
if request and hasattr(request, 'state') and hasattr(request.state, 'api_key'):
|
|
104
|
+
api_key = request.state.api_key
|
|
105
|
+
return api_key
|
|
106
|
+
|
|
107
|
+
# Get the API key configuration
|
|
108
|
+
from golf.auth.api_key import get_api_key_config
|
|
109
|
+
api_key_config = get_api_key_config()
|
|
110
|
+
|
|
111
|
+
if api_key_config and request:
|
|
112
|
+
# Extract API key from headers
|
|
113
|
+
header_name = api_key_config.header_name
|
|
114
|
+
header_prefix = api_key_config.header_prefix
|
|
115
|
+
|
|
116
|
+
# Case-insensitive header lookup
|
|
117
|
+
api_key = None
|
|
118
|
+
for k, v in request.headers.items():
|
|
119
|
+
if k.lower() == header_name.lower():
|
|
120
|
+
api_key = v
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
# Strip prefix if configured
|
|
124
|
+
if api_key and header_prefix and api_key.startswith(header_prefix):
|
|
125
|
+
api_key = api_key[len(header_prefix):]
|
|
126
|
+
|
|
127
|
+
if api_key:
|
|
128
|
+
return api_key
|
|
129
|
+
except (ImportError, RuntimeError) as e:
|
|
130
|
+
# FastMCP not available or not in HTTP context
|
|
131
|
+
pass
|
|
132
|
+
except Exception as e:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
# Final fallback: environment variable (for development/testing)
|
|
136
|
+
import os
|
|
137
|
+
env_api_key = os.environ.get('API_KEY')
|
|
138
|
+
if env_api_key:
|
|
139
|
+
return env_api_key
|
|
140
|
+
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def get_api_key_from_request(request) -> Optional[str]:
|
|
144
|
+
"""Get the API key from a specific request object.
|
|
145
|
+
|
|
146
|
+
This is useful when you have direct access to the request object.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
request: The Starlette Request object
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The API key if available, None otherwise
|
|
153
|
+
"""
|
|
154
|
+
# Check request state first (set by our middleware)
|
|
155
|
+
if hasattr(request, 'state') and hasattr(request.state, 'api_key'):
|
|
156
|
+
return request.state.api_key
|
|
157
|
+
|
|
158
|
+
# Fall back to context variable
|
|
159
|
+
return _current_api_key.get()
|
|
160
|
+
|
|
161
|
+
def debug_api_key_context() -> Dict[str, Any]:
|
|
162
|
+
"""Debug function to inspect API key context.
|
|
163
|
+
|
|
164
|
+
Returns a dictionary with debugging information about the current
|
|
165
|
+
API key context. Useful for troubleshooting authentication issues.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary with debug information
|
|
169
|
+
"""
|
|
170
|
+
import asyncio
|
|
171
|
+
import sys
|
|
172
|
+
import os
|
|
173
|
+
|
|
174
|
+
debug_info = {
|
|
175
|
+
"context_var_value": _current_api_key.get(),
|
|
176
|
+
"has_async_task": False,
|
|
177
|
+
"task_id": None,
|
|
178
|
+
"main_module_has_storage": False,
|
|
179
|
+
"main_module_has_context": False,
|
|
180
|
+
"request_id_from_context": None,
|
|
181
|
+
"env_vars": {
|
|
182
|
+
"API_KEY": bool(os.environ.get('API_KEY')),
|
|
183
|
+
"GOLF_API_KEY_DEBUG": os.environ.get('GOLF_API_KEY_DEBUG', 'false')
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
task = asyncio.current_task()
|
|
189
|
+
if task:
|
|
190
|
+
debug_info["has_async_task"] = True
|
|
191
|
+
debug_info["task_id"] = id(task)
|
|
192
|
+
except:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
main_module = sys.modules.get('__main__')
|
|
197
|
+
if main_module:
|
|
198
|
+
debug_info["main_module_has_storage"] = hasattr(main_module, 'api_key_storage')
|
|
199
|
+
debug_info["main_module_has_context"] = hasattr(main_module, 'request_id_context')
|
|
200
|
+
|
|
201
|
+
if hasattr(main_module, 'request_id_context'):
|
|
202
|
+
request_id_context = getattr(main_module, 'request_id_context')
|
|
203
|
+
debug_info["request_id_from_context"] = request_id_context.get()
|
|
204
|
+
except:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
return debug_info
|
|
@@ -106,7 +106,7 @@ def build_dev(
|
|
|
106
106
|
if not project_root:
|
|
107
107
|
console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
|
|
108
108
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
109
|
-
track_event("cli_build_failed", {"success": False, "environment": "dev"})
|
|
109
|
+
track_event("cli_build_failed", {"success": False, "environment": "dev", "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
|
|
110
110
|
raise typer.Exit(code=1)
|
|
111
111
|
|
|
112
112
|
# Load settings from the found project
|
|
@@ -124,8 +124,10 @@ def build_dev(
|
|
|
124
124
|
build_project(project_root, settings, output_dir, build_env="dev", copy_env=True)
|
|
125
125
|
# Track successful build with environment
|
|
126
126
|
track_event("cli_build_success", {"success": True, "environment": "dev"})
|
|
127
|
-
except Exception:
|
|
128
|
-
|
|
127
|
+
except Exception as e:
|
|
128
|
+
error_type = type(e).__name__
|
|
129
|
+
error_message = str(e)
|
|
130
|
+
track_event("cli_build_failed", {"success": False, "environment": "dev", "error_type": error_type, "error_message": error_message})
|
|
129
131
|
raise
|
|
130
132
|
|
|
131
133
|
|
|
@@ -142,7 +144,7 @@ def build_prod(
|
|
|
142
144
|
if not project_root:
|
|
143
145
|
console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
|
|
144
146
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
145
|
-
track_event("cli_build_failed", {"success": False, "environment": "prod"})
|
|
147
|
+
track_event("cli_build_failed", {"success": False, "environment": "prod", "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
|
|
146
148
|
raise typer.Exit(code=1)
|
|
147
149
|
|
|
148
150
|
# Load settings from the found project
|
|
@@ -160,8 +162,10 @@ def build_prod(
|
|
|
160
162
|
build_project(project_root, settings, output_dir, build_env="prod", copy_env=False)
|
|
161
163
|
# Track successful build with environment
|
|
162
164
|
track_event("cli_build_success", {"success": True, "environment": "prod"})
|
|
163
|
-
except Exception:
|
|
164
|
-
|
|
165
|
+
except Exception as e:
|
|
166
|
+
error_type = type(e).__name__
|
|
167
|
+
error_message = str(e)
|
|
168
|
+
track_event("cli_build_failed", {"success": False, "environment": "prod", "error_type": error_type, "error_message": error_message})
|
|
165
169
|
raise
|
|
166
170
|
|
|
167
171
|
|
|
@@ -191,7 +195,7 @@ def run(
|
|
|
191
195
|
if not project_root:
|
|
192
196
|
console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
|
|
193
197
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
194
|
-
track_event("cli_run_failed", {"success": False})
|
|
198
|
+
track_event("cli_run_failed", {"success": False, "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
|
|
195
199
|
raise typer.Exit(code=1)
|
|
196
200
|
|
|
197
201
|
# Load settings from the found project
|
|
@@ -207,13 +211,20 @@ def run(
|
|
|
207
211
|
if not dist_dir.exists():
|
|
208
212
|
if build_first:
|
|
209
213
|
console.print(f"[yellow]Dist directory {dist_dir} not found. Building first...[/yellow]")
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
try:
|
|
215
|
+
# Build the project
|
|
216
|
+
from golf.commands.build import build_project
|
|
217
|
+
build_project(project_root, settings, dist_dir)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
error_type = type(e).__name__
|
|
220
|
+
error_message = str(e)
|
|
221
|
+
console.print(f"[bold red]Error building project:[/bold red] {error_message}")
|
|
222
|
+
track_event("cli_run_failed", {"success": False, "error_type": f"BuildError.{error_type}", "error_message": error_message})
|
|
223
|
+
raise
|
|
213
224
|
else:
|
|
214
225
|
console.print(f"[bold red]Error: Dist directory {dist_dir} not found.[/bold red]")
|
|
215
226
|
console.print("Run 'golf build' first or use --build to build automatically.")
|
|
216
|
-
track_event("cli_run_failed", {"success": False})
|
|
227
|
+
track_event("cli_run_failed", {"success": False, "error_type": "DistNotFound", "error_message": "Dist directory not found"})
|
|
217
228
|
raise typer.Exit(code=1)
|
|
218
229
|
|
|
219
230
|
try:
|
|
@@ -231,13 +242,15 @@ def run(
|
|
|
231
242
|
if return_code == 0:
|
|
232
243
|
track_event("cli_run_success", {"success": True})
|
|
233
244
|
else:
|
|
234
|
-
track_event("cli_run_failed", {"success": False})
|
|
245
|
+
track_event("cli_run_failed", {"success": False, "error_type": "NonZeroExit", "error_message": f"Server exited with code {return_code}"})
|
|
235
246
|
|
|
236
247
|
# Exit with the same code as the server
|
|
237
248
|
if return_code != 0:
|
|
238
249
|
raise typer.Exit(code=return_code)
|
|
239
|
-
except Exception:
|
|
240
|
-
|
|
250
|
+
except Exception as e:
|
|
251
|
+
error_type = type(e).__name__
|
|
252
|
+
error_message = str(e)
|
|
253
|
+
track_event("cli_run_failed", {"success": False, "error_type": error_type, "error_message": error_message})
|
|
241
254
|
raise
|
|
242
255
|
|
|
243
256
|
|
|
@@ -9,7 +9,7 @@ from rich.console import Console
|
|
|
9
9
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
10
|
from rich.prompt import Confirm
|
|
11
11
|
|
|
12
|
-
from golf.core.telemetry import track_event
|
|
12
|
+
from golf.core.telemetry import track_event, track_command
|
|
13
13
|
|
|
14
14
|
console = Console()
|
|
15
15
|
|
|
@@ -26,72 +26,83 @@ def initialize_project(
|
|
|
26
26
|
output_dir: Directory where the project will be created
|
|
27
27
|
template: Template to use (basic or api_key)
|
|
28
28
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if
|
|
29
|
+
try:
|
|
30
|
+
# Validate template
|
|
31
|
+
valid_templates = ("basic", "api_key")
|
|
32
|
+
if template not in valid_templates:
|
|
33
|
+
console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
|
|
34
|
+
console.print(f"Available templates: {', '.join(valid_templates)}")
|
|
35
|
+
track_command("init", success=False, error_type="InvalidTemplate", error_message=f"Unknown template: {template}")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Check if directory exists
|
|
39
|
+
if output_dir.exists():
|
|
40
|
+
if not output_dir.is_dir():
|
|
41
|
+
console.print(
|
|
42
|
+
f"[bold red]Error:[/bold red] '{output_dir}' exists but is not a directory."
|
|
43
|
+
)
|
|
44
|
+
track_command("init", success=False, error_type="NotADirectory", error_message="Target exists but is not a directory")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Check if directory is empty
|
|
48
|
+
if any(output_dir.iterdir()):
|
|
49
|
+
if not Confirm.ask(
|
|
50
|
+
f"Directory '{output_dir}' is not empty. Continue anyway?",
|
|
51
|
+
default=False,
|
|
52
|
+
):
|
|
53
|
+
console.print("Initialization cancelled.")
|
|
54
|
+
track_event("cli_init_cancelled", {"success": False})
|
|
55
|
+
return
|
|
56
|
+
else:
|
|
57
|
+
# Create the directory
|
|
58
|
+
output_dir.mkdir(parents=True)
|
|
59
|
+
|
|
60
|
+
# Find template directory within the installed package
|
|
61
|
+
import golf
|
|
62
|
+
package_init_file = Path(golf.__file__)
|
|
63
|
+
# The 'examples' directory is now inside the 'golf' package directory
|
|
64
|
+
# e.g. golf/examples/basic, so go up one from __init__.py to get to 'golf'
|
|
65
|
+
template_dir = package_init_file.parent / "examples" / template
|
|
66
|
+
|
|
67
|
+
if not template_dir.exists():
|
|
40
68
|
console.print(
|
|
41
|
-
f"[bold red]Error:[/bold red] '{
|
|
69
|
+
f"[bold red]Error:[/bold red] Could not find template '{template}'"
|
|
42
70
|
)
|
|
43
|
-
|
|
71
|
+
track_command("init", success=False, error_type="TemplateNotFound", error_message=f"Template directory not found: {template}")
|
|
44
72
|
return
|
|
45
73
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# Create the directory
|
|
57
|
-
output_dir.mkdir(parents=True)
|
|
58
|
-
|
|
59
|
-
# Find template directory within the installed package
|
|
60
|
-
import golf
|
|
61
|
-
package_init_file = Path(golf.__file__)
|
|
62
|
-
# The 'examples' directory is now inside the 'golf' package directory
|
|
63
|
-
# e.g. golf/examples/basic, so go up one from __init__.py to get to 'golf'
|
|
64
|
-
template_dir = package_init_file.parent / "examples" / template
|
|
65
|
-
|
|
66
|
-
if not template_dir.exists():
|
|
67
|
-
console.print(
|
|
68
|
-
f"[bold red]Error:[/bold red] Could not find template '{template}'"
|
|
69
|
-
)
|
|
70
|
-
track_event("cli_init_failed", {"success": False})
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
# Copy template files
|
|
74
|
-
with Progress(
|
|
75
|
-
SpinnerColumn(),
|
|
76
|
-
TextColumn("[bold green]Creating project structure...[/bold green]"),
|
|
77
|
-
transient=True,
|
|
78
|
-
) as progress:
|
|
79
|
-
progress.add_task("copying", total=None)
|
|
74
|
+
# Copy template files
|
|
75
|
+
with Progress(
|
|
76
|
+
SpinnerColumn(),
|
|
77
|
+
TextColumn("[bold green]Creating project structure...[/bold green]"),
|
|
78
|
+
transient=True,
|
|
79
|
+
) as progress:
|
|
80
|
+
progress.add_task("copying", total=None)
|
|
81
|
+
|
|
82
|
+
# Copy directory structure
|
|
83
|
+
_copy_template(template_dir, output_dir, project_name)
|
|
80
84
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
# Create virtual environment
|
|
86
|
+
console.print("[bold green]Project initialized successfully![/bold green]")
|
|
87
|
+
console.print(f"\nTo get started, run:")
|
|
88
|
+
console.print(f" cd {output_dir.name}")
|
|
89
|
+
console.print(f" golf build dev")
|
|
90
|
+
|
|
91
|
+
# Track successful initialization
|
|
92
|
+
track_event("cli_init_success", {
|
|
93
|
+
"success": True,
|
|
94
|
+
"template": template
|
|
95
|
+
})
|
|
96
|
+
except Exception as e:
|
|
97
|
+
# Capture error details for telemetry
|
|
98
|
+
error_type = type(e).__name__
|
|
99
|
+
error_message = str(e)
|
|
100
|
+
|
|
101
|
+
console.print(f"[bold red]Error during initialization:[/bold red] {error_message}")
|
|
102
|
+
track_command("init", success=False, error_type=error_type, error_message=error_message)
|
|
103
|
+
|
|
104
|
+
# Re-raise to maintain existing behavior
|
|
105
|
+
raise
|
|
95
106
|
|
|
96
107
|
|
|
97
108
|
def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> None:
|
|
@@ -20,6 +20,7 @@ from golf.core.parser import (
|
|
|
20
20
|
from golf.core.transformer import transform_component
|
|
21
21
|
from golf.core.builder_auth import generate_auth_code, generate_auth_routes
|
|
22
22
|
from golf.auth import get_auth_config
|
|
23
|
+
from golf.auth.api_key import get_api_key_config
|
|
23
24
|
from golf.core.builder_telemetry import (
|
|
24
25
|
generate_telemetry_imports,
|
|
25
26
|
get_otel_dependencies
|
|
@@ -548,8 +549,7 @@ class CodeGenerator:
|
|
|
548
549
|
# Add imports section for different transport methods
|
|
549
550
|
if self.settings.transport == "sse":
|
|
550
551
|
imports.append("import uvicorn")
|
|
551
|
-
|
|
552
|
-
elif self.settings.transport != "stdio":
|
|
552
|
+
elif self.settings.transport in ["streamable-http", "http"]:
|
|
553
553
|
imports.append("import uvicorn")
|
|
554
554
|
|
|
555
555
|
# Get transport-specific configuration
|
|
@@ -735,12 +735,6 @@ class CodeGenerator:
|
|
|
735
735
|
server_code_lines.append(mcp_instance_line)
|
|
736
736
|
server_code_lines.append("")
|
|
737
737
|
|
|
738
|
-
# Add any post-init code from auth
|
|
739
|
-
post_init_code = []
|
|
740
|
-
if auth_components.get("has_auth") and auth_components.get("post_init_code"):
|
|
741
|
-
post_init_code.extend(auth_components["post_init_code"])
|
|
742
|
-
post_init_code.append("")
|
|
743
|
-
|
|
744
738
|
# Main entry point with transport-specific app initialization
|
|
745
739
|
main_code = [
|
|
746
740
|
"if __name__ == \"__main__\":",
|
|
@@ -764,16 +758,46 @@ class CodeGenerator:
|
|
|
764
758
|
|
|
765
759
|
# Transport-specific run methods
|
|
766
760
|
if self.settings.transport == "sse":
|
|
761
|
+
# Check if we need to add API key middleware for SSE
|
|
762
|
+
api_key_config = get_api_key_config()
|
|
763
|
+
if auth_components.get("has_auth") and api_key_config:
|
|
764
|
+
main_code.extend([
|
|
765
|
+
" # For SSE with API key auth, we need to get the app and add middleware",
|
|
766
|
+
" app = mcp.http_app(transport=\"sse\")",
|
|
767
|
+
" app.add_middleware(ApiKeyMiddleware)",
|
|
768
|
+
])
|
|
769
|
+
else:
|
|
770
|
+
main_code.extend([
|
|
771
|
+
" # For SSE, get the app to add middleware",
|
|
772
|
+
" app = mcp.http_app(transport=\"sse\")",
|
|
773
|
+
])
|
|
774
|
+
|
|
775
|
+
# Add OpenTelemetry middleware to the SSE app if enabled
|
|
776
|
+
if self.settings.opentelemetry_enabled:
|
|
777
|
+
main_code.extend([
|
|
778
|
+
" # Apply OpenTelemetry middleware to the SSE app",
|
|
779
|
+
" from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware",
|
|
780
|
+
" app = OpenTelemetryMiddleware(app)",
|
|
781
|
+
])
|
|
782
|
+
|
|
767
783
|
main_code.extend([
|
|
768
|
-
" #
|
|
769
|
-
"
|
|
784
|
+
" # Run with the configured app",
|
|
785
|
+
" uvicorn.run(app, host=host, port=port, log_level=\"info\")"
|
|
770
786
|
])
|
|
771
|
-
elif self.settings.transport
|
|
787
|
+
elif self.settings.transport in ["streamable-http", "http"]:
|
|
772
788
|
main_code.extend([
|
|
773
789
|
" # Create HTTP app and run with uvicorn",
|
|
774
790
|
" app = mcp.http_app()",
|
|
775
791
|
])
|
|
776
792
|
|
|
793
|
+
# Check if we need to add API key middleware
|
|
794
|
+
api_key_config = get_api_key_config()
|
|
795
|
+
if auth_components.get("has_auth") and api_key_config:
|
|
796
|
+
main_code.extend([
|
|
797
|
+
" # Add API key middleware",
|
|
798
|
+
" app.add_middleware(ApiKeyMiddleware)",
|
|
799
|
+
])
|
|
800
|
+
|
|
777
801
|
# Add OpenTelemetry middleware to the HTTP app if enabled
|
|
778
802
|
if self.settings.opentelemetry_enabled:
|
|
779
803
|
main_code.extend([
|
|
@@ -800,7 +824,6 @@ class CodeGenerator:
|
|
|
800
824
|
env_section +
|
|
801
825
|
auth_setup_code +
|
|
802
826
|
server_code_lines +
|
|
803
|
-
post_init_code +
|
|
804
827
|
component_registrations +
|
|
805
828
|
main_code
|
|
806
829
|
)
|