golf-mcp 0.2.0__tar.gz → 0.2.3__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 (111) hide show
  1. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/MANIFEST.in +1 -0
  2. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/PKG-INFO +1 -1
  3. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/pyproject.toml +2 -2
  4. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/__init__.py +1 -1
  5. golf_mcp-0.2.3/src/golf/_endpoints.py.in +6 -0
  6. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/auth/__init__.py +48 -0
  7. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/auth/factory.py +27 -0
  8. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/auth/providers.py +219 -1
  9. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/builder_auth.py +3 -3
  10. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf_mcp.egg-info/PKG-INFO +1 -1
  11. golf_mcp-0.2.3/src/golf_mcp.egg-info/SOURCES.txt +63 -0
  12. golf_mcp-0.2.0/src/golf/examples/basic/.coverage +0 -0
  13. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/.gitignore +0 -2
  14. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/class_index.html +0 -547
  15. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +0 -733
  16. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
  17. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/function_index.html +0 -2091
  18. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/index.html +0 -349
  19. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
  20. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/status.json +0 -1
  21. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/style_cb_8e611ae1.css +0 -337
  22. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +0 -323
  23. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +0 -170
  24. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +0 -430
  25. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +0 -288
  26. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +0 -493
  27. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +0 -353
  28. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +0 -120
  29. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +0 -1535
  30. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +0 -98
  31. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +0 -289
  32. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +0 -476
  33. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +0 -97
  34. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +0 -102
  35. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +0 -178
  36. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +0 -387
  37. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +0 -222
  38. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +0 -106
  39. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +0 -107
  40. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +0 -98
  41. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +0 -306
  42. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +0 -329
  43. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +0 -1471
  44. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +0 -186
  45. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +0 -315
  46. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +0 -1149
  47. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +0 -279
  48. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +0 -589
  49. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +0 -286
  50. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +0 -107
  51. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +0 -417
  52. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +0 -109
  53. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +0 -109
  54. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +0 -150
  55. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +0 -267
  56. golf_mcp-0.2.0/src/golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +0 -318
  57. golf_mcp-0.2.0/src/golf_mcp.egg-info/SOURCES.txt +0 -107
  58. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/.docs/docs.md +0 -0
  59. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/.docs/fastmcp-diff.md +0 -0
  60. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/.docs/mcp.md +0 -0
  61. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/LICENSE +0 -0
  62. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/README.md +0 -0
  63. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/setup.cfg +0 -0
  64. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/setup.py +0 -0
  65. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/_endpoints_fallback.py +0 -0
  66. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/auth/api_key.py +0 -0
  67. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/auth/helpers.py +0 -0
  68. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/auth/registry.py +0 -0
  69. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/cli/__init__.py +0 -0
  70. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/cli/branding.py +0 -0
  71. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/cli/main.py +0 -0
  72. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/commands/__init__.py +0 -0
  73. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/commands/build.py +0 -0
  74. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/commands/init.py +0 -0
  75. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/commands/run.py +0 -0
  76. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/__init__.py +0 -0
  77. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/builder.py +0 -0
  78. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/builder_metrics.py +0 -0
  79. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/builder_telemetry.py +0 -0
  80. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/config.py +0 -0
  81. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/parser.py +0 -0
  82. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/platform.py +0 -0
  83. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/telemetry.py +0 -0
  84. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/core/transformer.py +0 -0
  85. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/__init__.py +0 -0
  86. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/.env.example +0 -0
  87. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/README.md +0 -0
  88. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/auth.py +0 -0
  89. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/golf.json +0 -0
  90. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/prompts/welcome.py +0 -0
  91. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/resources/current_time.py +0 -0
  92. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/resources/info.py +0 -0
  93. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/resources/weather/city.py +0 -0
  94. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/resources/weather/common.py +0 -0
  95. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/resources/weather/current.py +0 -0
  96. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
  97. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/tools/calculator.py +0 -0
  98. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/examples/basic/tools/say/hello.py +0 -0
  99. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/metrics/__init__.py +0 -0
  100. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/metrics/collector.py +0 -0
  101. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/metrics/registry.py +0 -0
  102. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/telemetry/__init__.py +0 -0
  103. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/telemetry/instrumentation.py +0 -0
  104. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/utilities/__init__.py +0 -0
  105. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/utilities/context.py +0 -0
  106. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/utilities/elicitation.py +0 -0
  107. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf/utilities/sampling.py +0 -0
  108. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  109. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  110. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf_mcp.egg-info/requires.txt +0 -0
  111. {golf_mcp-0.2.0 → golf_mcp-0.2.3}/src/golf_mcp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  include README.md
2
2
  include LICENSE
3
3
  include pyproject.toml
4
+ include src/golf/_endpoints.py.in
4
5
  graft .docs
5
6
  graft src/golf/examples
6
7
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.2.0
3
+ Version: 0.2.3
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.2.0"
7
+ version = "0.2.3"
8
8
  description = "Framework for building MCP servers"
9
9
  authors = [
10
10
  {name = "Antoni Gmitruk", email = "antoni@golf.dev"}
@@ -66,7 +66,7 @@ golf = ["examples/**/*"]
66
66
 
67
67
  [tool.poetry]
68
68
  name = "golf-mcp"
69
- version = "0.1.20"
69
+ version = "0.2.3"
70
70
  description = "Framework for building MCP servers with zero boilerplate"
71
71
  authors = ["Antoni Gmitruk <antoni@golf.dev>"]
72
72
  license = "Apache-2.0"
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.0"
1
+ __version__ = "0.2.3"
2
2
 
3
3
  # Import endpoints with fallback for dev mode
4
4
  try:
@@ -0,0 +1,6 @@
1
+ # Auto-generated at build time by setup.py:build_py
2
+ # This template contains placeholders that are replaced during build
3
+
4
+ # Platform endpoints
5
+ PLATFORM_API_URL = "{PLATFORM_API_URL}"
6
+ OTEL_ENDPOINT = "{OTEL_ENDPOINT}"
@@ -14,6 +14,7 @@ from .providers import (
14
14
  StaticTokenConfig,
15
15
  OAuthServerConfig,
16
16
  RemoteAuthConfig,
17
+ OAuthProxyConfig,
17
18
  )
18
19
  from .factory import (
19
20
  create_auth_provider,
@@ -44,6 +45,7 @@ __all__ = [
44
45
  "configure_auth",
45
46
  "configure_jwt_auth",
46
47
  "configure_dev_auth",
48
+ "configure_oauth_proxy",
47
49
  "get_auth_config",
48
50
  # Provider configurations
49
51
  "AuthConfig",
@@ -51,6 +53,7 @@ __all__ = [
51
53
  "StaticTokenConfig",
52
54
  "OAuthServerConfig",
53
55
  "RemoteAuthConfig",
56
+ "OAuthProxyConfig",
54
57
  # Factory functions
55
58
  "create_auth_provider",
56
59
  "create_simple_jwt_provider",
@@ -191,6 +194,51 @@ def configure_dev_auth(
191
194
  configure_auth(config)
192
195
 
193
196
 
197
+ def configure_oauth_proxy(
198
+ upstream_authorization_endpoint: str,
199
+ upstream_token_endpoint: str,
200
+ upstream_client_id: str,
201
+ upstream_client_secret: str,
202
+ base_url: str,
203
+ token_verifier_config: JWTAuthConfig | StaticTokenConfig,
204
+ scopes_supported: list[str] | None = None,
205
+ upstream_revocation_endpoint: str | None = None,
206
+ redirect_path: str = "/oauth/callback",
207
+ ) -> None:
208
+ """Configure OAuth proxy authentication for non-DCR providers.
209
+
210
+ This sets up an OAuth proxy that bridges MCP clients (expecting DCR) with
211
+ traditional OAuth providers like GitHub, Google, Okta Web Apps that use
212
+ fixed client credentials.
213
+
214
+ Args:
215
+ upstream_authorization_endpoint: Provider's authorization URL
216
+ upstream_token_endpoint: Provider's token endpoint URL
217
+ upstream_client_id: Your client ID registered with the provider
218
+ upstream_client_secret: Your client secret from the provider
219
+ base_url: This proxy server's public URL
220
+ token_verifier_config: JWT or static token config for token verification
221
+ scopes_supported: Scopes to advertise to MCP clients
222
+ upstream_revocation_endpoint: Optional token revocation endpoint
223
+ redirect_path: OAuth callback path (default: /oauth/callback)
224
+
225
+ Note:
226
+ Requires golf-mcp-enterprise package for implementation.
227
+ """
228
+ config = OAuthProxyConfig(
229
+ upstream_authorization_endpoint=upstream_authorization_endpoint,
230
+ upstream_token_endpoint=upstream_token_endpoint,
231
+ upstream_client_id=upstream_client_id,
232
+ upstream_client_secret=upstream_client_secret,
233
+ upstream_revocation_endpoint=upstream_revocation_endpoint,
234
+ base_url=base_url,
235
+ redirect_path=redirect_path,
236
+ scopes_supported=scopes_supported or [],
237
+ token_verifier_config=token_verifier_config,
238
+ )
239
+ configure_auth(config)
240
+
241
+
194
242
  def get_auth_config() -> AuthConfig | None:
195
243
  """Get the current auth configuration.
196
244
 
@@ -17,6 +17,7 @@ from .providers import (
17
17
  StaticTokenConfig,
18
18
  OAuthServerConfig,
19
19
  RemoteAuthConfig,
20
+ OAuthProxyConfig,
20
21
  )
21
22
  from .registry import (
22
23
  get_provider_registry,
@@ -55,6 +56,8 @@ def create_auth_provider(config: AuthConfig) -> "AuthProvider":
55
56
  return _create_oauth_server_provider(config)
56
57
  elif config.provider_type == "remote":
57
58
  return _create_remote_provider(config)
59
+ elif config.provider_type == "oauth_proxy":
60
+ return _create_oauth_proxy_provider(config)
58
61
  else:
59
62
  raise ValueError(f"Unknown provider type: {config.provider_type}") from None
60
63
 
@@ -238,6 +241,11 @@ def _create_remote_provider(config: RemoteAuthConfig) -> "AuthProvider":
238
241
  if not hasattr(token_verifier, "verify_token"):
239
242
  raise ValueError(f"Remote auth provider requires a TokenVerifier, got {type(token_verifier).__name__}")
240
243
 
244
+ # Update token verifier's required_scopes to match our scopes_supported for PRM
245
+ # RemoteAuthProvider uses token_verifier.required_scopes for scopes_supported in PRM
246
+ if config.scopes_supported and hasattr(token_verifier, "required_scopes"):
247
+ token_verifier.required_scopes = list(config.scopes_supported)
248
+
241
249
  return RemoteAuthProvider(
242
250
  token_verifier=token_verifier,
243
251
  authorization_servers=authorization_servers,
@@ -245,6 +253,22 @@ def _create_remote_provider(config: RemoteAuthConfig) -> "AuthProvider":
245
253
  )
246
254
 
247
255
 
256
+ def _create_oauth_proxy_provider(config: OAuthProxyConfig) -> "AuthProvider":
257
+ """Create OAuth proxy provider - requires enterprise package."""
258
+ try:
259
+ # Try to import from enterprise package
260
+ from golf_enterprise import create_oauth_proxy_provider
261
+
262
+ return create_oauth_proxy_provider(config)
263
+ except ImportError as e:
264
+ raise ImportError(
265
+ "OAuth Proxy requires golf-mcp-enterprise package. "
266
+ "This feature provides OAuth proxy functionality for non-DCR providers "
267
+ "(GitHub, Google, Okta Web Apps, etc.). "
268
+ "Contact sales@golf.dev for enterprise licensing."
269
+ ) from e
270
+
271
+
248
272
  def create_simple_jwt_provider(
249
273
  *,
250
274
  jwks_uri: str | None = None,
@@ -319,6 +343,8 @@ def register_builtin_providers() -> None:
319
343
  - static: Static token verification (development)
320
344
  - oauth_server: Full OAuth authorization server
321
345
  - remote: Remote authorization server integration
346
+
347
+ Note: oauth_proxy provider is registered by the golf-mcp-enterprise package
322
348
  """
323
349
  registry = get_provider_registry()
324
350
 
@@ -327,6 +353,7 @@ def register_builtin_providers() -> None:
327
353
  registry.register_factory("static", _create_static_provider)
328
354
  registry.register_factory("oauth_server", _create_oauth_server_provider)
329
355
  registry.register_factory("remote", _create_remote_provider)
356
+ # oauth_proxy is registered by golf-mcp-enterprise package when installed
330
357
 
331
358
 
332
359
  # Register built-in providers when module is imported
@@ -299,6 +299,12 @@ class RemoteAuthConfig(BaseModel):
299
299
  # This server's URL
300
300
  resource_server_url: str = Field(..., description="URL of this resource server")
301
301
 
302
+ # Scopes this resource supports (advertised via /.well-known/oauth-protected-resource)
303
+ scopes_supported: list[str] = Field(
304
+ default_factory=list,
305
+ description="Scopes this resource supports (advertised via /.well-known/oauth-protected-resource)",
306
+ )
307
+
302
308
  # Token verification (delegate to another config)
303
309
  token_verifier_config: JWTAuthConfig | StaticTokenConfig = Field(
304
310
  ..., description="Configuration for the underlying token verifier"
@@ -362,6 +368,45 @@ class RemoteAuthConfig(BaseModel):
362
368
 
363
369
  return url
364
370
 
371
+ @field_validator("scopes_supported")
372
+ @classmethod
373
+ def validate_scopes_supported(cls, v: list[str]) -> list[str]:
374
+ """Validate scopes_supported format and security."""
375
+ if not v:
376
+ return v
377
+
378
+ cleaned_scopes = []
379
+ for scope in v:
380
+ scope = scope.strip()
381
+ if not scope:
382
+ raise ValueError("Scopes cannot be empty or whitespace-only")
383
+
384
+ # OAuth 2.0 scope format validation (RFC 6749)
385
+ if not all(32 < ord(c) < 127 and c not in ' "\\' for c in scope):
386
+ raise ValueError(
387
+ f"Invalid scope format: '{scope}' - must be ASCII printable without spaces, quotes, or backslashes"
388
+ )
389
+
390
+ # Reasonable length limit to prevent abuse
391
+ if len(scope) > 128:
392
+ raise ValueError(f"Scope too long: '{scope}' - maximum 128 characters")
393
+
394
+ # Warn about potentially dangerous scope names
395
+ dangerous_scopes = {"admin", "root", "superuser", "system", "*", "all"}
396
+ if scope.lower() in dangerous_scopes:
397
+ import warnings
398
+
399
+ warnings.warn(
400
+ f"Potentially dangerous scope detected: '{scope}'. "
401
+ "Consider using more specific, principle-of-least-privilege scopes.",
402
+ UserWarning,
403
+ stacklevel=2,
404
+ )
405
+
406
+ cleaned_scopes.append(scope)
407
+
408
+ return cleaned_scopes
409
+
365
410
  @model_validator(mode="after")
366
411
  def validate_token_verifier_compatibility(self) -> "RemoteAuthConfig":
367
412
  """Validate that the token verifier config is compatible with token verification."""
@@ -389,8 +434,181 @@ class RemoteAuthConfig(BaseModel):
389
434
  if isinstance(config, StaticTokenConfig) and not config.tokens:
390
435
  raise ValueError("Static token verifier config must provide at least one token")
391
436
 
437
+ # Convenience: if user didn't set scopes_supported, default to verifier.required_scopes
438
+ if not self.scopes_supported:
439
+ if hasattr(config, "required_scopes") and config.required_scopes:
440
+ self.scopes_supported = list(config.required_scopes)
441
+
442
+ return self
443
+
444
+
445
+ class OAuthProxyConfig(BaseModel):
446
+ """Configuration for OAuth proxy functionality (requires golf-mcp-enterprise).
447
+
448
+ This configuration enables bridging MCP clients (which expect Dynamic Client
449
+ Registration) with OAuth providers that use fixed client credentials like
450
+ GitHub Apps, Google Cloud Console apps, Okta Web Applications, etc.
451
+
452
+ The proxy acts as a DCR-capable authorization server to MCP clients while
453
+ using your fixed upstream client credentials with the actual OAuth provider.
454
+
455
+ Note: This class provides configuration only. The actual implementation
456
+ requires the golf-mcp-enterprise package.
457
+ """
458
+
459
+ provider_type: Literal["oauth_proxy"] = "oauth_proxy"
460
+
461
+ # Upstream OAuth provider configuration
462
+ upstream_authorization_endpoint: str = Field(..., description="Upstream provider's authorization endpoint URL")
463
+ upstream_token_endpoint: str = Field(..., description="Upstream provider's token endpoint URL")
464
+ upstream_client_id: str = Field(..., description="Your registered client ID with the upstream provider")
465
+ upstream_client_secret: str = Field(..., description="Your registered client secret with the upstream provider")
466
+ upstream_revocation_endpoint: str | None = Field(None, description="Optional upstream token revocation endpoint")
467
+
468
+ # This proxy server configuration
469
+ base_url: str = Field(..., description="Public URL of this OAuth proxy server")
470
+ redirect_path: str = Field("/oauth/callback", description="OAuth callback path (must match provider registration)")
471
+
472
+ # Scopes and token verification
473
+ scopes_supported: list[str] = Field(default_factory=list, description="Scopes supported by this proxy")
474
+ token_verifier_config: JWTAuthConfig | StaticTokenConfig = Field(
475
+ ..., description="Token verifier configuration for validating upstream tokens"
476
+ )
477
+
478
+ # Environment variable names for runtime configuration
479
+ upstream_authorization_endpoint_env_var: str | None = Field(
480
+ None, description="Environment variable name for upstream authorization endpoint"
481
+ )
482
+ upstream_token_endpoint_env_var: str | None = Field(
483
+ None, description="Environment variable name for upstream token endpoint"
484
+ )
485
+ upstream_client_id_env_var: str | None = Field(None, description="Environment variable name for upstream client ID")
486
+ upstream_client_secret_env_var: str | None = Field(
487
+ None, description="Environment variable name for upstream client secret"
488
+ )
489
+ upstream_revocation_endpoint_env_var: str | None = Field(
490
+ None, description="Environment variable name for upstream revocation endpoint"
491
+ )
492
+ base_url_env_var: str | None = Field(None, description="Environment variable name for base URL")
493
+
494
+ @field_validator("upstream_authorization_endpoint", "upstream_token_endpoint", "base_url")
495
+ @classmethod
496
+ def validate_required_urls(cls, v: str) -> str:
497
+ """Validate required URLs are properly formatted."""
498
+ if not v or not v.strip():
499
+ raise ValueError("URL cannot be empty")
500
+
501
+ url = v.strip()
502
+ try:
503
+ from urllib.parse import urlparse
504
+
505
+ parsed = urlparse(url)
506
+ if not parsed.scheme or not parsed.netloc:
507
+ raise ValueError(f"Invalid URL format: '{url}' - must include scheme and netloc")
508
+ if parsed.scheme not in ("http", "https"):
509
+ raise ValueError(f"URL must use http or https scheme: '{url}'")
510
+ except Exception as e:
511
+ if isinstance(e, ValueError):
512
+ raise
513
+ raise ValueError(f"Invalid URL '{url}': {e}") from e
514
+
515
+ return url
516
+
517
+ @field_validator("upstream_revocation_endpoint")
518
+ @classmethod
519
+ def validate_optional_url(cls, v: str | None) -> str | None:
520
+ """Validate optional URLs are properly formatted."""
521
+ if not v:
522
+ return v
523
+
524
+ url = v.strip()
525
+ if not url:
526
+ return None
527
+
528
+ try:
529
+ from urllib.parse import urlparse
530
+
531
+ parsed = urlparse(url)
532
+ if not parsed.scheme or not parsed.netloc:
533
+ raise ValueError(f"Invalid URL format: '{url}' - must include scheme and netloc")
534
+ if parsed.scheme not in ("http", "https"):
535
+ raise ValueError(f"URL must use http or https scheme: '{url}'")
536
+ except Exception as e:
537
+ if isinstance(e, ValueError):
538
+ raise
539
+ raise ValueError(f"Invalid URL '{url}': {e}") from e
540
+
541
+ return url
542
+
543
+ @field_validator("scopes_supported")
544
+ @classmethod
545
+ def validate_scopes_supported(cls, v: list[str]) -> list[str]:
546
+ """Validate scopes_supported format and security."""
547
+ if not v:
548
+ return v
549
+
550
+ cleaned_scopes = []
551
+ for scope in v:
552
+ scope = scope.strip()
553
+ if not scope:
554
+ raise ValueError("Scopes cannot be empty or whitespace-only")
555
+
556
+ # OAuth 2.0 scope format validation (RFC 6749)
557
+ if not all(32 < ord(c) < 127 and c not in ' "\\' for c in scope):
558
+ raise ValueError(
559
+ f"Invalid scope format: '{scope}' - must be ASCII printable without spaces, quotes, or backslashes"
560
+ )
561
+
562
+ # Reasonable length limit to prevent abuse
563
+ if len(scope) > 128:
564
+ raise ValueError(f"Scope too long: '{scope}' - maximum 128 characters")
565
+
566
+ cleaned_scopes.append(scope)
567
+
568
+ return cleaned_scopes
569
+
570
+ @model_validator(mode="after")
571
+ def validate_oauth_proxy_config(self) -> "OAuthProxyConfig":
572
+ """Validate OAuth proxy configuration consistency."""
573
+ # Validate token verifier config is compatible
574
+ if not isinstance(self.token_verifier_config, JWTAuthConfig | StaticTokenConfig):
575
+ raise ValueError(
576
+ f"token_verifier_config must be JWTAuthConfig or StaticTokenConfig, got {type(self.token_verifier_config).__name__}"
577
+ )
578
+
579
+ # Warn about HTTPS requirements in production
580
+ is_production = (
581
+ os.environ.get("GOLF_ENV", "").lower() in ("prod", "production")
582
+ or os.environ.get("NODE_ENV", "").lower() == "production"
583
+ or os.environ.get("ENVIRONMENT", "").lower() in ("prod", "production")
584
+ )
585
+
586
+ if is_production:
587
+ from urllib.parse import urlparse
588
+
589
+ urls_to_check = [
590
+ ("base_url", self.base_url),
591
+ ("upstream_authorization_endpoint", self.upstream_authorization_endpoint),
592
+ ("upstream_token_endpoint", self.upstream_token_endpoint),
593
+ ]
594
+
595
+ if self.upstream_revocation_endpoint:
596
+ urls_to_check.append(("upstream_revocation_endpoint", self.upstream_revocation_endpoint))
597
+
598
+ for field_name, url in urls_to_check:
599
+ parsed = urlparse(url)
600
+ if parsed.scheme == "http":
601
+ import warnings
602
+
603
+ warnings.warn(
604
+ f"OAuth proxy {field_name} '{url}' uses HTTP in production environment. "
605
+ "HTTPS is strongly recommended for OAuth endpoints to prevent token interception.",
606
+ UserWarning,
607
+ stacklevel=2,
608
+ )
609
+
392
610
  return self
393
611
 
394
612
 
395
613
  # Union type for all auth configurations
396
- AuthConfig = JWTAuthConfig | StaticTokenConfig | OAuthServerConfig | RemoteAuthConfig
614
+ AuthConfig = JWTAuthConfig | StaticTokenConfig | OAuthServerConfig | RemoteAuthConfig | OAuthProxyConfig
@@ -52,7 +52,7 @@ def generate_auth_code(
52
52
  "import os",
53
53
  "import sys",
54
54
  "from golf.auth.factory import create_auth_provider",
55
- "from golf.auth.providers import RemoteAuthConfig, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig",
55
+ "from golf.auth.providers import RemoteAuthConfig, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig, OAuthProxyConfig",
56
56
  ]
57
57
 
58
58
  # Embed the auth configuration directly in the generated code
@@ -64,7 +64,7 @@ def generate_auth_code(
64
64
  f"auth_config = {auth_config_repr}",
65
65
  "try:",
66
66
  " auth_provider = create_auth_provider(auth_config)",
67
- " print(f'Authentication configured with {auth_config.provider_type} provider')",
67
+ " # Authentication configured with {auth_config.provider_type} provider",
68
68
  "except Exception as e:",
69
69
  " print(f'Authentication setup failed: {e}', file=sys.stderr)",
70
70
  " auth_provider = None",
@@ -203,7 +203,7 @@ if auth_provider and hasattr(auth_provider, 'get_routes'):
203
203
  # Add routes to FastMCP's additional HTTP routes list
204
204
  try:
205
205
  mcp._additional_http_routes.extend(auth_routes)
206
- print(f"Added {len(auth_routes)} OAuth metadata routes")
206
+ # Added {len(auth_routes)} OAuth metadata routes
207
207
  except Exception as e:
208
208
  print(f"Warning: Failed to add OAuth routes: {e}")
209
209
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.2.0
3
+ Version: 0.2.3
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -0,0 +1,63 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ .docs/docs.md
7
+ .docs/fastmcp-diff.md
8
+ .docs/mcp.md
9
+ src/golf/__init__.py
10
+ src/golf/_endpoints.py.in
11
+ src/golf/_endpoints_fallback.py
12
+ src/golf/auth/__init__.py
13
+ src/golf/auth/api_key.py
14
+ src/golf/auth/factory.py
15
+ src/golf/auth/helpers.py
16
+ src/golf/auth/providers.py
17
+ src/golf/auth/registry.py
18
+ src/golf/cli/__init__.py
19
+ src/golf/cli/branding.py
20
+ src/golf/cli/main.py
21
+ src/golf/commands/__init__.py
22
+ src/golf/commands/build.py
23
+ src/golf/commands/init.py
24
+ src/golf/commands/run.py
25
+ src/golf/core/__init__.py
26
+ src/golf/core/builder.py
27
+ src/golf/core/builder_auth.py
28
+ src/golf/core/builder_metrics.py
29
+ src/golf/core/builder_telemetry.py
30
+ src/golf/core/config.py
31
+ src/golf/core/parser.py
32
+ src/golf/core/platform.py
33
+ src/golf/core/telemetry.py
34
+ src/golf/core/transformer.py
35
+ src/golf/examples/__init__.py
36
+ src/golf/examples/basic/.env.example
37
+ src/golf/examples/basic/README.md
38
+ src/golf/examples/basic/auth.py
39
+ src/golf/examples/basic/golf.json
40
+ src/golf/examples/basic/prompts/welcome.py
41
+ src/golf/examples/basic/resources/current_time.py
42
+ src/golf/examples/basic/resources/info.py
43
+ src/golf/examples/basic/resources/weather/city.py
44
+ src/golf/examples/basic/resources/weather/common.py
45
+ src/golf/examples/basic/resources/weather/current.py
46
+ src/golf/examples/basic/resources/weather/forecast.py
47
+ src/golf/examples/basic/tools/calculator.py
48
+ src/golf/examples/basic/tools/say/hello.py
49
+ src/golf/metrics/__init__.py
50
+ src/golf/metrics/collector.py
51
+ src/golf/metrics/registry.py
52
+ src/golf/telemetry/__init__.py
53
+ src/golf/telemetry/instrumentation.py
54
+ src/golf/utilities/__init__.py
55
+ src/golf/utilities/context.py
56
+ src/golf/utilities/elicitation.py
57
+ src/golf/utilities/sampling.py
58
+ src/golf_mcp.egg-info/PKG-INFO
59
+ src/golf_mcp.egg-info/SOURCES.txt
60
+ src/golf_mcp.egg-info/dependency_links.txt
61
+ src/golf_mcp.egg-info/entry_points.txt
62
+ src/golf_mcp.egg-info/requires.txt
63
+ src/golf_mcp.egg-info/top_level.txt
@@ -1,2 +0,0 @@
1
- # Created by coverage.py
2
- *