golf-mcp 0.1.10__tar.gz → 0.1.12__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.

Files changed (74) hide show
  1. {golf_mcp-0.1.10/src/golf_mcp.egg-info → golf_mcp-0.1.12}/PKG-INFO +1 -1
  2. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/pyproject.toml +33 -5
  3. golf_mcp-0.1.12/src/golf/__init__.py +1 -0
  4. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/auth/__init__.py +38 -26
  5. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/auth/api_key.py +16 -23
  6. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/auth/helpers.py +68 -54
  7. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/auth/oauth.py +340 -277
  8. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/auth/provider.py +58 -53
  9. golf_mcp-0.1.12/src/golf/cli/__init__.py +1 -0
  10. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/cli/main.py +202 -82
  11. golf_mcp-0.1.12/src/golf/commands/__init__.py +3 -0
  12. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/commands/build.py +31 -25
  13. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/commands/init.py +119 -80
  14. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/commands/run.py +14 -13
  15. golf_mcp-0.1.12/src/golf/core/__init__.py +1 -0
  16. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/core/builder.py +478 -353
  17. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/core/builder_auth.py +115 -107
  18. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/core/builder_telemetry.py +12 -9
  19. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/core/config.py +62 -46
  20. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/core/parser.py +174 -136
  21. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/core/telemetry.py +169 -69
  22. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/core/transformer.py +53 -55
  23. golf_mcp-0.1.12/src/golf/examples/__init__.py +0 -0
  24. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/pre_build.py +2 -2
  25. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/tools/issues/create.py +35 -36
  26. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/tools/issues/list.py +42 -37
  27. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/tools/repos/list.py +50 -29
  28. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/tools/search/code.py +50 -37
  29. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/tools/users/get.py +21 -20
  30. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/pre_build.py +4 -4
  31. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/prompts/welcome.py +6 -7
  32. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/resources/current_time.py +10 -9
  33. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/resources/info.py +6 -5
  34. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/resources/weather/common.py +16 -10
  35. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/resources/weather/current.py +15 -11
  36. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/resources/weather/forecast.py +15 -11
  37. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/tools/github_user.py +19 -21
  38. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/tools/hello.py +10 -6
  39. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/tools/payments/charge.py +34 -25
  40. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/tools/payments/common.py +8 -6
  41. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/tools/payments/refund.py +29 -25
  42. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/telemetry/__init__.py +6 -6
  43. golf_mcp-0.1.12/src/golf/telemetry/instrumentation.py +1045 -0
  44. {golf_mcp-0.1.10 → golf_mcp-0.1.12/src/golf_mcp.egg-info}/PKG-INFO +1 -1
  45. golf_mcp-0.1.10/src/golf/__init__.py +0 -1
  46. golf_mcp-0.1.10/src/golf/cli/__init__.py +0 -1
  47. golf_mcp-0.1.10/src/golf/commands/__init__.py +0 -3
  48. golf_mcp-0.1.10/src/golf/core/__init__.py +0 -1
  49. golf_mcp-0.1.10/src/golf/examples/__init__.py +0 -1
  50. golf_mcp-0.1.10/src/golf/telemetry/instrumentation.py +0 -540
  51. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/.docs/docs.md +0 -0
  52. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/.docs/fast-mcp.md +0 -0
  53. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/.docs/fastmcp-example-1.py +0 -0
  54. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/.docs/fastmcp-example-2.py +0 -0
  55. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/.docs/mcp.md +0 -0
  56. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/.docs/oauth-implementation.md +0 -0
  57. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/.docs/oauth.md +0 -0
  58. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/LICENSE +0 -0
  59. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/MANIFEST.in +0 -0
  60. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/README.md +0 -0
  61. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/setup.cfg +0 -0
  62. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/.env +0 -0
  63. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/.env.example +0 -0
  64. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/README.md +0 -0
  65. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/api_key/golf.json +0 -0
  66. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/.env +0 -0
  67. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/.env.example +0 -0
  68. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/README.md +0 -0
  69. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf/examples/basic/golf.json +0 -0
  70. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf_mcp.egg-info/SOURCES.txt +0 -0
  71. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  72. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  73. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf_mcp.egg-info/requires.txt +0 -0
  74. {golf_mcp-0.1.10 → golf_mcp-0.1.12}/src/golf_mcp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "golf-mcp"
7
- version = "0.1.10"
7
+ version = "0.1.12"
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.10"
67
+ version = "0.1.12"
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"
@@ -115,11 +115,20 @@ pytest-cov = "^4.1.0"
115
115
  line-length = 88
116
116
 
117
117
  [tool.ruff]
118
- select = ["E", "F", "I", "B", "C4", "C90", "UP", "N", "ANN", "SIM", "TID"]
119
- ignore = ["ANN101", "ANN102", "ANN401"]
120
118
  line-length = 88
121
119
  target-version = "py311"
122
120
 
121
+ [tool.ruff.lint]
122
+ select = ["E", "F", "B", "C4", "C90", "UP", "N", "ANN", "SIM", "TID"]
123
+ ignore = ["ANN401"]
124
+
125
+ [tool.ruff.format]
126
+ # Use Black-compatible formatting
127
+ quote-style = "double"
128
+ indent-style = "space"
129
+ skip-magic-trailing-comma = false
130
+ line-ending = "auto"
131
+
123
132
  [tool.mypy]
124
133
  python_version = "3.11"
125
134
  disallow_untyped_defs = true
@@ -131,4 +140,23 @@ strict_optional = true
131
140
  warn_redundant_casts = true
132
141
  warn_unused_ignores = true
133
142
  warn_return_any = true
134
- warn_unused_configs = true
143
+ warn_unused_configs = true
144
+
145
+ [tool.pytest.ini_options]
146
+ testpaths = ["tests"]
147
+ python_files = ["test_*.py", "*_test.py"]
148
+ python_classes = ["Test*"]
149
+ python_functions = ["test_*"]
150
+ asyncio_mode = "auto"
151
+ addopts = [
152
+ "-v",
153
+ "--strict-markers",
154
+ "--tb=short",
155
+ "--cov=golf",
156
+ "--cov-report=term-missing",
157
+ "--cov-report=html",
158
+ ]
159
+ markers = [
160
+ "slow: marks tests as slow (deselect with '-m \"not slow\"')",
161
+ "integration: marks tests as integration tests",
162
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.12"
@@ -9,24 +9,32 @@ from typing import List, Optional, Tuple
9
9
 
10
10
  from mcp.server.auth.settings import AuthSettings, ClientRegistrationOptions
11
11
 
12
- from .provider import ProviderConfig
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, debug_api_key_context
15
12
  from .api_key import configure_api_key, get_api_key_config, is_api_key_configured
13
+ from .helpers import (
14
+ debug_api_key_context,
15
+ extract_token_from_header,
16
+ get_access_token,
17
+ get_api_key,
18
+ get_provider_token,
19
+ set_api_key,
20
+ )
21
+ from .oauth import GolfOAuthProvider, create_callback_handler
22
+ from .provider import ProviderConfig
23
+
16
24
 
17
25
  class AuthConfig:
18
26
  """Configuration for OAuth authentication in GolfMCP."""
19
-
27
+
20
28
  def __init__(
21
- self,
29
+ self,
22
30
  provider_config: ProviderConfig,
23
- required_scopes: List[str],
31
+ required_scopes: list[str],
24
32
  callback_path: str = "/auth/callback",
25
33
  login_path: str = "/login",
26
- error_path: str = "/auth-error"
27
- ):
34
+ error_path: str = "/auth-error",
35
+ ) -> None:
28
36
  """Initialize authentication configuration.
29
-
37
+
30
38
  Args:
31
39
  provider_config: Configuration for the OAuth provider
32
40
  required_scopes: Scopes required for all authenticated requests
@@ -39,34 +47,36 @@ class AuthConfig:
39
47
  self.callback_path = callback_path
40
48
  self.login_path = login_path
41
49
  self.error_path = error_path
42
-
50
+
43
51
  # Create the OAuth provider
44
52
  self.provider = GolfOAuthProvider(provider_config)
45
-
53
+
46
54
  # Create auth settings for FastMCP
47
55
  self.auth_settings = AuthSettings(
48
56
  issuer_url=provider_config.issuer_url or "http://localhost:3000",
49
57
  client_registration_options=ClientRegistrationOptions(
50
58
  enabled=True,
51
59
  valid_scopes=provider_config.scopes,
52
- default_scopes=provider_config.scopes
60
+ default_scopes=provider_config.scopes,
53
61
  ),
54
- required_scopes=required_scopes or provider_config.scopes
62
+ required_scopes=required_scopes or provider_config.scopes,
55
63
  )
56
64
 
65
+
57
66
  # Global state for the build process
58
- _auth_config: Optional[AuthConfig] = None
67
+ _auth_config: AuthConfig | None = None
68
+
59
69
 
60
70
  def configure_auth(
61
71
  provider_config=None,
62
72
  provider=None,
63
- required_scopes: Optional[List[str]] = None,
73
+ required_scopes: list[str] | None = None,
64
74
  callback_path: str = "/auth/callback",
65
75
  ) -> None:
66
76
  """Configure authentication for a GolfMCP server.
67
-
77
+
68
78
  This function should be called in pre_build.py to set up authentication.
69
-
79
+
70
80
  Args:
71
81
  provider_config: Configuration for the OAuth provider (new parameter name)
72
82
  provider: Configuration for the OAuth provider (old parameter name, deprecated)
@@ -75,22 +85,23 @@ def configure_auth(
75
85
  public_paths: List of paths that don't require authentication (deprecated, no longer used)
76
86
  """
77
87
  global _auth_config
78
-
88
+
79
89
  # Handle backward compatibility with old parameter name
80
90
  if provider_config is None and provider is not None:
81
91
  provider_config = provider
82
92
  elif provider_config is None and provider is None:
83
93
  raise ValueError("Either provider_config or provider must be provided")
84
-
94
+
85
95
  _auth_config = AuthConfig(
86
96
  provider_config=provider_config,
87
97
  required_scopes=required_scopes or provider_config.scopes,
88
- callback_path=callback_path
98
+ callback_path=callback_path,
89
99
  )
90
100
 
91
- def get_auth_config() -> Tuple[Optional[ProviderConfig], List[str]]:
101
+
102
+ def get_auth_config() -> tuple[ProviderConfig | None, list[str]]:
92
103
  """Get the current authentication configuration.
93
-
104
+
94
105
  Returns:
95
106
  Tuple of (provider_config, required_scopes)
96
107
  """
@@ -98,13 +109,14 @@ def get_auth_config() -> Tuple[Optional[ProviderConfig], List[str]]:
98
109
  return _auth_config.provider_config, _auth_config.required_scopes
99
110
  return None, []
100
111
 
101
- def create_auth_provider() -> Optional[GolfOAuthProvider]:
112
+
113
+ def create_auth_provider() -> GolfOAuthProvider | None:
102
114
  """Create an OAuth provider from the configured provider settings.
103
-
115
+
104
116
  Returns:
105
117
  GolfOAuthProvider instance or None if not configured
106
118
  """
107
119
  if not _auth_config:
108
120
  return None
109
-
110
- return _auth_config.provider
121
+
122
+ return _auth_config.provider
@@ -5,56 +5,51 @@ allowing tools to access API keys from request headers and forward them to
5
5
  upstream services.
6
6
  """
7
7
 
8
- from typing import Optional
9
8
  from pydantic import BaseModel, Field
10
9
 
11
10
 
12
11
  class ApiKeyConfig(BaseModel):
13
12
  """Configuration for API key authentication."""
14
-
13
+
15
14
  header_name: str = Field(
16
- "X-API-Key",
17
- description="Name of the header containing the API key"
15
+ "X-API-Key", description="Name of the header containing the API key"
18
16
  )
19
17
  header_prefix: str = Field(
20
18
  "",
21
- description="Optional prefix to strip from the header value (e.g., 'Bearer ')"
19
+ description="Optional prefix to strip from the header value (e.g., 'Bearer ')",
22
20
  )
23
21
  required: bool = Field(
24
- True,
25
- description="Whether API key is required for all requests"
22
+ True, description="Whether API key is required for all requests"
26
23
  )
27
24
 
28
25
 
29
26
  # Global configuration storage
30
- _api_key_config: Optional[ApiKeyConfig] = None
27
+ _api_key_config: ApiKeyConfig | None = None
31
28
 
32
29
 
33
30
  def configure_api_key(
34
- header_name: str = "X-API-Key",
35
- header_prefix: str = "",
36
- required: bool = True
31
+ header_name: str = "X-API-Key", header_prefix: str = "", required: bool = True
37
32
  ) -> None:
38
33
  """Configure API key extraction from request headers.
39
-
34
+
40
35
  This function should be called in pre_build.py to set up API key handling.
41
-
36
+
42
37
  Args:
43
38
  header_name: Name of the header containing the API key (default: "X-API-Key")
44
39
  header_prefix: Optional prefix to strip from the header value (e.g., "Bearer ")
45
40
  required: Whether API key is required for all requests (default: True)
46
-
41
+
47
42
  Example:
48
43
  # In pre_build.py
49
44
  from golf.auth.api_key import configure_api_key
50
-
45
+
51
46
  # Require API key for all requests
52
47
  configure_api_key(
53
48
  header_name="Authorization",
54
49
  header_prefix="Bearer ",
55
50
  required=True
56
51
  )
57
-
52
+
58
53
  # Or make API key optional (pass-through mode)
59
54
  configure_api_key(
60
55
  header_name="Authorization",
@@ -64,15 +59,13 @@ def configure_api_key(
64
59
  """
65
60
  global _api_key_config
66
61
  _api_key_config = ApiKeyConfig(
67
- header_name=header_name,
68
- header_prefix=header_prefix,
69
- required=required
62
+ header_name=header_name, header_prefix=header_prefix, required=required
70
63
  )
71
64
 
72
65
 
73
- def get_api_key_config() -> Optional[ApiKeyConfig]:
66
+ def get_api_key_config() -> ApiKeyConfig | None:
74
67
  """Get the current API key configuration.
75
-
68
+
76
69
  Returns:
77
70
  The API key configuration if set, None otherwise
78
71
  """
@@ -81,8 +74,8 @@ def get_api_key_config() -> Optional[ApiKeyConfig]:
81
74
 
82
75
  def is_api_key_configured() -> bool:
83
76
  """Check if API key authentication is configured.
84
-
77
+
85
78
  Returns:
86
79
  True if API key authentication is configured, False otherwise
87
80
  """
88
- return _api_key_config is not None
81
+ return _api_key_config is not None
@@ -1,7 +1,7 @@
1
1
  """Helper functions for working with authentication in MCP context."""
2
2
 
3
- from typing import Optional, Dict, Any
4
3
  from contextvars import ContextVar
4
+ from typing import Any
5
5
 
6
6
  # Re-export get_access_token from the MCP SDK
7
7
  from mcp.server.auth.middleware.auth_context import get_access_token
@@ -9,10 +9,11 @@ from mcp.server.auth.middleware.auth_context import get_access_token
9
9
  from .oauth import GolfOAuthProvider
10
10
 
11
11
  # Context variable to store the active OAuth provider
12
- _active_golf_oauth_provider: Optional[GolfOAuthProvider] = None
12
+ _active_golf_oauth_provider: GolfOAuthProvider | None = None
13
13
 
14
14
  # Context variable to store the current request's API key
15
- _current_api_key: ContextVar[Optional[str]] = ContextVar('current_api_key', default=None)
15
+ _current_api_key: ContextVar[str | None] = ContextVar("current_api_key", default=None)
16
+
16
17
 
17
18
  def _set_active_golf_oauth_provider(provider: GolfOAuthProvider) -> None:
18
19
  """
@@ -22,14 +23,15 @@ def _set_active_golf_oauth_provider(provider: GolfOAuthProvider) -> None:
22
23
  global _active_golf_oauth_provider
23
24
  _active_golf_oauth_provider = provider
24
25
 
25
- def get_provider_token() -> Optional[str]:
26
+
27
+ def get_provider_token() -> str | None:
26
28
  """
27
29
  Get a provider token (e.g., GitHub token) associated with the current
28
30
  MCP session's access token.
29
31
 
30
32
  This relies on _set_active_golf_oauth_provider being called at server startup.
31
33
  """
32
- mcp_access_token = get_access_token() # From MCP SDK, uses its own ContextVar
34
+ mcp_access_token = get_access_token() # From MCP SDK, uses its own ContextVar
33
35
  if not mcp_access_token:
34
36
  # No active MCP session token.
35
37
  return None
@@ -37,14 +39,15 @@ def get_provider_token() -> Optional[str]:
37
39
  provider = _active_golf_oauth_provider
38
40
  if not provider:
39
41
  return None
40
-
42
+
41
43
  if not hasattr(provider, "get_provider_token"):
42
44
  return None
43
45
 
44
46
  # Call the get_provider_token method on the actual GolfOAuthProvider instance
45
47
  return provider.get_provider_token(mcp_access_token.token)
46
48
 
47
- def extract_token_from_header(auth_header: str) -> Optional[str]:
49
+
50
+ def extract_token_from_header(auth_header: str) -> str | None:
48
51
  """Extract bearer token from Authorization header.
49
52
 
50
53
  Args:
@@ -57,39 +60,41 @@ def extract_token_from_header(auth_header: str) -> Optional[str]:
57
60
  return None
58
61
 
59
62
  parts = auth_header.split()
60
- if len(parts) != 2 or parts[0].lower() != 'bearer':
63
+ if len(parts) != 2 or parts[0].lower() != "bearer":
61
64
  return None
62
65
 
63
66
  return parts[1]
64
67
 
65
- def set_api_key(api_key: Optional[str]) -> None:
68
+
69
+ def set_api_key(api_key: str | None) -> None:
66
70
  """Set the API key for the current request context.
67
-
71
+
68
72
  This is an internal function used by the middleware.
69
-
73
+
70
74
  Args:
71
75
  api_key: The API key to store in the context
72
76
  """
73
77
  _current_api_key.set(api_key)
74
78
 
75
- def get_api_key() -> Optional[str]:
79
+
80
+ def get_api_key() -> str | None:
76
81
  """Get the API key from the current request context.
77
-
82
+
78
83
  This function should be used in tools to retrieve the API key
79
84
  that was sent in the request headers.
80
-
85
+
81
86
  Returns:
82
87
  The API key if available, None otherwise
83
-
88
+
84
89
  Example:
85
90
  # In a tool file
86
91
  from golf.auth import get_api_key
87
-
92
+
88
93
  async def call_api():
89
94
  api_key = get_api_key()
90
95
  if not api_key:
91
96
  return {"error": "No API key provided"}
92
-
97
+
93
98
  # Use the API key in your request
94
99
  headers = {"Authorization": f"Bearer {api_key}"}
95
100
  ...
@@ -98,79 +103,84 @@ def get_api_key() -> Optional[str]:
98
103
  try:
99
104
  # This follows the FastMCP pattern for accessing HTTP requests
100
105
  from fastmcp.server.dependencies import get_http_request
106
+
101
107
  request = get_http_request()
102
-
103
- if request and hasattr(request, 'state') and hasattr(request.state, 'api_key'):
108
+
109
+ if request and hasattr(request, "state") and hasattr(request.state, "api_key"):
104
110
  api_key = request.state.api_key
105
111
  return api_key
106
-
112
+
107
113
  # Get the API key configuration
108
114
  from golf.auth.api_key import get_api_key_config
115
+
109
116
  api_key_config = get_api_key_config()
110
-
117
+
111
118
  if api_key_config and request:
112
119
  # Extract API key from headers
113
120
  header_name = api_key_config.header_name
114
121
  header_prefix = api_key_config.header_prefix
115
-
122
+
116
123
  # Case-insensitive header lookup
117
124
  api_key = None
118
125
  for k, v in request.headers.items():
119
126
  if k.lower() == header_name.lower():
120
127
  api_key = v
121
128
  break
122
-
129
+
123
130
  # Strip prefix if configured
124
131
  if api_key and header_prefix and api_key.startswith(header_prefix):
125
- api_key = api_key[len(header_prefix):]
126
-
132
+ api_key = api_key[len(header_prefix) :]
133
+
127
134
  if api_key:
128
135
  return api_key
129
- except (ImportError, RuntimeError) as e:
136
+ except (ImportError, RuntimeError):
130
137
  # FastMCP not available or not in HTTP context
131
138
  pass
132
- except Exception as e:
139
+ except Exception:
133
140
  pass
134
-
141
+
135
142
  # Final fallback: environment variable (for development/testing)
136
143
  import os
137
- env_api_key = os.environ.get('API_KEY')
144
+
145
+ env_api_key = os.environ.get("API_KEY")
138
146
  if env_api_key:
139
147
  return env_api_key
140
-
148
+
141
149
  return None
142
150
 
143
- def get_api_key_from_request(request) -> Optional[str]:
151
+
152
+ def get_api_key_from_request(request) -> str | None:
144
153
  """Get the API key from a specific request object.
145
-
154
+
146
155
  This is useful when you have direct access to the request object.
147
-
156
+
148
157
  Args:
149
158
  request: The Starlette Request object
150
-
159
+
151
160
  Returns:
152
161
  The API key if available, None otherwise
153
162
  """
154
163
  # Check request state first (set by our middleware)
155
- if hasattr(request, 'state') and hasattr(request.state, 'api_key'):
164
+ if hasattr(request, "state") and hasattr(request.state, "api_key"):
156
165
  return request.state.api_key
157
-
166
+
158
167
  # Fall back to context variable
159
168
  return _current_api_key.get()
160
169
 
161
- def debug_api_key_context() -> Dict[str, Any]:
170
+
171
+ def debug_api_key_context() -> dict[str, Any]:
162
172
  """Debug function to inspect API key context.
163
-
173
+
164
174
  Returns a dictionary with debugging information about the current
165
175
  API key context. Useful for troubleshooting authentication issues.
166
-
176
+
167
177
  Returns:
168
178
  Dictionary with debug information
169
179
  """
170
180
  import asyncio
171
- import sys
172
181
  import os
173
-
182
+ import sys
183
+
174
184
  debug_info = {
175
185
  "context_var_value": _current_api_key.get(),
176
186
  "has_async_task": False,
@@ -179,11 +189,11 @@ def debug_api_key_context() -> Dict[str, Any]:
179
189
  "main_module_has_context": False,
180
190
  "request_id_from_context": None,
181
191
  "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
- }
192
+ "API_KEY": bool(os.environ.get("API_KEY")),
193
+ "GOLF_API_KEY_DEBUG": os.environ.get("GOLF_API_KEY_DEBUG", "false"),
194
+ },
185
195
  }
186
-
196
+
187
197
  try:
188
198
  task = asyncio.current_task()
189
199
  if task:
@@ -191,17 +201,21 @@ def debug_api_key_context() -> Dict[str, Any]:
191
201
  debug_info["task_id"] = id(task)
192
202
  except:
193
203
  pass
194
-
204
+
195
205
  try:
196
- main_module = sys.modules.get('__main__')
206
+ main_module = sys.modules.get("__main__")
197
207
  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')
208
+ debug_info["main_module_has_storage"] = hasattr(
209
+ main_module, "api_key_storage"
210
+ )
211
+ debug_info["main_module_has_context"] = hasattr(
212
+ main_module, "request_id_context"
213
+ )
214
+
215
+ if hasattr(main_module, "request_id_context"):
216
+ request_id_context = main_module.request_id_context
203
217
  debug_info["request_id_from_context"] = request_id_context.get()
204
218
  except:
205
219
  pass
206
-
207
- return debug_info
220
+
221
+ return debug_info