golf-mcp 0.1.4__tar.gz → 0.1.6__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 (65) hide show
  1. {golf_mcp-0.1.4/src/golf_mcp.egg-info → golf_mcp-0.1.6}/PKG-INFO +15 -20
  2. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/README.md +14 -19
  3. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/pyproject.toml +2 -2
  4. golf_mcp-0.1.6/src/golf/__init__.py +1 -0
  5. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/__init__.py +2 -1
  6. golf_mcp-0.1.6/src/golf/auth/api_key.py +73 -0
  7. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/helpers.py +45 -4
  8. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/cli/main.py +1 -1
  9. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/init.py +4 -3
  10. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/builder.py +10 -1
  11. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/builder_auth.py +64 -0
  12. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/telemetry.py +5 -5
  13. golf_mcp-0.1.6/src/golf/examples/api_key/README.md +70 -0
  14. golf_mcp-0.1.6/src/golf/examples/api_key/golf.json +7 -0
  15. golf_mcp-0.1.6/src/golf/examples/api_key/pre_build.py +10 -0
  16. golf_mcp-0.1.6/src/golf/examples/api_key/tools/issues/create.py +94 -0
  17. golf_mcp-0.1.6/src/golf/examples/api_key/tools/issues/list.py +87 -0
  18. golf_mcp-0.1.6/src/golf/examples/api_key/tools/repos/list.py +90 -0
  19. golf_mcp-0.1.6/src/golf/examples/api_key/tools/search/code.py +93 -0
  20. golf_mcp-0.1.6/src/golf/examples/api_key/tools/users/get.py +81 -0
  21. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/golf.json +1 -3
  22. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/hello.py +4 -3
  23. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/payments/charge.py +15 -4
  24. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/payments/refund.py +19 -12
  25. {golf_mcp-0.1.4 → golf_mcp-0.1.6/src/golf_mcp.egg-info}/PKG-INFO +15 -20
  26. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/SOURCES.txt +9 -0
  27. golf_mcp-0.1.4/src/golf/__init__.py +0 -1
  28. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/docs.md +0 -0
  29. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/fast-mcp.md +0 -0
  30. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/fastmcp-example-1.py +0 -0
  31. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/fastmcp-example-2.py +0 -0
  32. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/mcp.md +0 -0
  33. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/oauth-implementation.md +0 -0
  34. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/oauth.md +0 -0
  35. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/LICENSE +0 -0
  36. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/MANIFEST.in +0 -0
  37. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/setup.cfg +0 -0
  38. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/oauth.py +0 -0
  39. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/provider.py +0 -0
  40. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/cli/__init__.py +0 -0
  41. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/__init__.py +0 -0
  42. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/build.py +0 -0
  43. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/run.py +0 -0
  44. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/__init__.py +0 -0
  45. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/builder_telemetry.py +0 -0
  46. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/config.py +0 -0
  47. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/parser.py +0 -0
  48. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/transformer.py +0 -0
  49. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/__init__.py +0 -0
  50. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/.env +0 -0
  51. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/.env.example +0 -0
  52. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/README.md +0 -0
  53. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/pre_build.py +0 -0
  54. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/prompts/welcome.py +0 -0
  55. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/current_time.py +0 -0
  56. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/info.py +0 -0
  57. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/weather/common.py +0 -0
  58. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/weather/current.py +0 -0
  59. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
  60. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/github_user.py +0 -0
  61. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/payments/common.py +0 -0
  62. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  63. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  64. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/requires.txt +0 -0
  65. {golf_mcp-0.1.4 → golf_mcp-0.1.6}/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.4
3
+ Version: 0.1.6
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -139,15 +139,16 @@ Creating a new tool is as simple as adding a Python file to the `tools/` directo
139
139
  # tools/hello.py
140
140
  """Hello World tool {{project_name}}."""
141
141
 
142
- from pydantic import BaseModel
142
+ from typing import Annotated
143
+ from pydantic import BaseModel, Field
143
144
 
144
145
  class Output(BaseModel):
145
146
  """Response from the hello tool."""
146
147
  message: str
147
148
 
148
149
  async def hello(
149
- name: str = "World",
150
- greeting: str = "Hello"
150
+ name: Annotated[str, Field(description="The name of the person to greet")] = "World",
151
+ greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
151
152
  ) -> Output:
152
153
  """Say hello to the given name.
153
154
 
@@ -188,9 +189,18 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
188
189
  - `"stdio"` enables integration with command-line tools and scripts
189
190
  - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
190
191
 
192
+ ## Roadmap
193
+
194
+ Here are the things we are working hard on:
195
+
196
+ * **Native OpenTelemetry implementation for tracing**
197
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
198
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
199
+
200
+
191
201
  ## Privacy & Telemetry
192
202
 
193
- Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
203
+ Golf collects **anonymous** usage data on the CLI to help us understand how the framework is being used and improve it over time. The data collected includes:
194
204
 
195
205
  - Commands run (init, build, run)
196
206
  - Success/failure status (no error details)
@@ -218,23 +228,8 @@ You can disable telemetry in several ways:
218
228
  golf init my-project --no-telemetry
219
229
  ```
220
230
 
221
- 3. **Environment variable** (temporary override):
222
- ```bash
223
- export GOLF_TELEMETRY=0
224
- golf init my-project
225
- ```
226
-
227
231
  Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
228
232
 
229
- ## Roadmap
230
-
231
- Here are the things we are working hard on:
232
-
233
- * **Native OpenTelemetry implementation for tracing**
234
- * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
235
- * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
236
-
237
-
238
233
  <div align="center">
239
234
  Made with ❤️ in Warsaw, Poland and SF
240
235
  </div>
@@ -101,15 +101,16 @@ Creating a new tool is as simple as adding a Python file to the `tools/` directo
101
101
  # tools/hello.py
102
102
  """Hello World tool {{project_name}}."""
103
103
 
104
- from pydantic import BaseModel
104
+ from typing import Annotated
105
+ from pydantic import BaseModel, Field
105
106
 
106
107
  class Output(BaseModel):
107
108
  """Response from the hello tool."""
108
109
  message: str
109
110
 
110
111
  async def hello(
111
- name: str = "World",
112
- greeting: str = "Hello"
112
+ name: Annotated[str, Field(description="The name of the person to greet")] = "World",
113
+ greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
113
114
  ) -> Output:
114
115
  """Say hello to the given name.
115
116
 
@@ -150,9 +151,18 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
150
151
  - `"stdio"` enables integration with command-line tools and scripts
151
152
  - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
152
153
 
154
+ ## Roadmap
155
+
156
+ Here are the things we are working hard on:
157
+
158
+ * **Native OpenTelemetry implementation for tracing**
159
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
160
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
161
+
162
+
153
163
  ## Privacy & Telemetry
154
164
 
155
- Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
165
+ Golf collects **anonymous** usage data on the CLI to help us understand how the framework is being used and improve it over time. The data collected includes:
156
166
 
157
167
  - Commands run (init, build, run)
158
168
  - Success/failure status (no error details)
@@ -180,23 +190,8 @@ You can disable telemetry in several ways:
180
190
  golf init my-project --no-telemetry
181
191
  ```
182
192
 
183
- 3. **Environment variable** (temporary override):
184
- ```bash
185
- export GOLF_TELEMETRY=0
186
- golf init my-project
187
- ```
188
-
189
193
  Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
190
194
 
191
- ## Roadmap
192
-
193
- Here are the things we are working hard on:
194
-
195
- * **Native OpenTelemetry implementation for tracing**
196
- * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
197
- * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
198
-
199
-
200
195
  <div align="center">
201
196
  Made with ❤️ in Warsaw, Poland and SF
202
197
  </div>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "golf-mcp"
7
- version = "0.1.4"
7
+ version = "0.1.6"
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.4"
67
+ version = "0.1.6"
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.6"
@@ -11,7 +11,8 @@ 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
14
+ from .helpers import get_access_token, get_provider_token, extract_token_from_header, get_api_key, set_api_key
15
+ from .api_key import configure_api_key, get_api_key_config, is_api_key_configured
15
16
 
16
17
  class AuthConfig:
17
18
  """Configuration for OAuth authentication in GolfMCP."""
@@ -0,0 +1,73 @@
1
+ """API Key authentication support for Golf MCP servers.
2
+
3
+ This module provides a simple API key pass-through mechanism for Golf servers,
4
+ allowing tools to access API keys from request headers and forward them to
5
+ upstream services.
6
+ """
7
+
8
+ from typing import Optional
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class ApiKeyConfig(BaseModel):
13
+ """Configuration for API key authentication."""
14
+
15
+ header_name: str = Field(
16
+ "X-API-Key",
17
+ description="Name of the header containing the API key"
18
+ )
19
+ header_prefix: str = Field(
20
+ "",
21
+ description="Optional prefix to strip from the header value (e.g., 'Bearer ')"
22
+ )
23
+
24
+
25
+ # Global configuration storage
26
+ _api_key_config: Optional[ApiKeyConfig] = None
27
+
28
+
29
+ def configure_api_key(
30
+ header_name: str = "X-API-Key",
31
+ header_prefix: str = ""
32
+ ) -> None:
33
+ """Configure API key extraction from request headers.
34
+
35
+ This function should be called in pre_build.py to set up API key handling.
36
+
37
+ Args:
38
+ header_name: Name of the header containing the API key (default: "X-API-Key")
39
+ header_prefix: Optional prefix to strip from the header value (e.g., "Bearer ")
40
+ case_sensitive: Whether header name matching should be case-sensitive
41
+
42
+ Example:
43
+ # In pre_build.py
44
+ from golf.auth.api_key import configure_api_key
45
+
46
+ configure_api_key(
47
+ header_name="Authorization",
48
+ header_prefix="Bearer "
49
+ )
50
+ """
51
+ global _api_key_config
52
+ _api_key_config = ApiKeyConfig(
53
+ header_name=header_name,
54
+ header_prefix=header_prefix
55
+ )
56
+
57
+
58
+ def get_api_key_config() -> Optional[ApiKeyConfig]:
59
+ """Get the current API key configuration.
60
+
61
+ Returns:
62
+ The API key configuration if set, None otherwise
63
+ """
64
+ return _api_key_config
65
+
66
+
67
+ def is_api_key_configured() -> bool:
68
+ """Check if API key authentication is configured.
69
+
70
+ Returns:
71
+ True if API key authentication is configured, False otherwise
72
+ """
73
+ return _api_key_config is not None
@@ -1,19 +1,26 @@
1
1
  """Helper functions for working with authentication in MCP context."""
2
2
 
3
3
  from typing import Optional
4
+ from contextvars import ContextVar
4
5
 
5
6
  # Re-export get_access_token from the MCP SDK
6
7
  from mcp.server.auth.middleware.auth_context import get_access_token
7
8
 
8
- _active_golf_oauth_provider = None
9
+ from .oauth import GolfOAuthProvider
9
10
 
10
- def _set_active_golf_oauth_provider(provider_instance) -> None:
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:
11
18
  """
12
19
  Sets the active GolfOAuthProvider instance.
13
20
  Should only be called once during server startup.
14
21
  """
15
22
  global _active_golf_oauth_provider
16
- _active_golf_oauth_provider = provider_instance
23
+ _active_golf_oauth_provider = provider
17
24
 
18
25
  def get_provider_token() -> Optional[str]:
19
26
  """
@@ -53,4 +60,38 @@ def extract_token_from_header(auth_header: str) -> Optional[str]:
53
60
  if len(parts) != 2 or parts[0].lower() != 'bearer':
54
61
  return None
55
62
 
56
- return parts[1]
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
+ return _current_api_key.get()
@@ -69,7 +69,7 @@ def init(
69
69
  None, "--output-dir", "-o", help="Directory to create the project in"
70
70
  ),
71
71
  template: str = typer.Option(
72
- "basic", "--template", "-t", help="Template to use (basic or advanced)"
72
+ "basic", "--template", "-t", help="Template to use (basic or api_key)"
73
73
  ),
74
74
  ) -> None:
75
75
  """Initialize a new GolfMCP project.
@@ -24,12 +24,13 @@ def initialize_project(
24
24
  Args:
25
25
  project_name: Name of the project
26
26
  output_dir: Directory where the project will be created
27
- template: Template to use (basic or advanced)
27
+ template: Template to use (basic or api_key)
28
28
  """
29
29
  # Validate template
30
- if template not in ("basic", "advanced"):
30
+ valid_templates = ("basic", "api_key")
31
+ if template not in valid_templates:
31
32
  console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
32
- console.print("Available templates: basic, advanced")
33
+ console.print(f"Available templates: {', '.join(valid_templates)}")
33
34
  track_event("cli_init_failed", {"success": False})
34
35
  return
35
36
 
@@ -624,7 +624,10 @@ class CodeGenerator:
624
624
  registration += f"\nmcp.add_tool({full_module_path}.{component.entry_function}"
625
625
  else:
626
626
  registration += f"\nmcp.add_tool({full_module_path}.export"
627
-
627
+
628
+ # Add the name parameter
629
+ registration += f", name=\"{component.name}\""
630
+
628
631
  # Add description from docstring
629
632
  if component.docstring:
630
633
  # Escape any quotes in the docstring
@@ -640,6 +643,9 @@ class CodeGenerator:
640
643
  registration += f"\nmcp.add_resource_fn({full_module_path}.{component.entry_function}, uri=\"{component.uri_template}\""
641
644
  else:
642
645
  registration += f"\nmcp.add_resource_fn({full_module_path}.export, uri=\"{component.uri_template}\""
646
+
647
+ # Add the name parameter
648
+ registration += f", name=\"{component.name}\""
643
649
 
644
650
  # Add description from docstring
645
651
  if component.docstring:
@@ -656,6 +662,9 @@ class CodeGenerator:
656
662
  registration += f"\nmcp.add_prompt({full_module_path}.{component.entry_function}"
657
663
  else:
658
664
  registration += f"\nmcp.add_prompt({full_module_path}.export"
665
+
666
+ # Add the name parameter
667
+ registration += f", name=\"{component.name}\""
659
668
 
660
669
  # Add description from docstring
661
670
  if component.docstring:
@@ -5,12 +5,19 @@ into the generated FastMCP application during the build process.
5
5
  """
6
6
 
7
7
  from golf.auth import get_auth_config
8
+ from golf.auth.api_key import get_api_key_config
8
9
 
9
10
 
10
11
  def generate_auth_code(server_name: str, host: str = "127.0.0.1", port: int = 3000, https: bool = False) -> str:
11
12
  """Generate code for setting up authentication in the FastMCP app.
12
13
  This code string will be injected into the generated server.py and executed at its runtime.
13
14
  """
15
+ # Check for API key configuration first
16
+ api_key_config = get_api_key_config()
17
+ if api_key_config:
18
+ return generate_api_key_auth_code(server_name)
19
+
20
+ # Otherwise check for OAuth configuration
14
21
  original_provider_config, required_scopes_from_config = get_auth_config()
15
22
 
16
23
  if not original_provider_config:
@@ -114,10 +121,67 @@ def generate_auth_code(server_name: str, host: str = "127.0.0.1", port: int = 30
114
121
  return "\n".join(generated_code_lines)
115
122
 
116
123
 
124
+ def generate_api_key_auth_code(server_name: str) -> str:
125
+ """Generate code for API key authentication middleware."""
126
+ api_key_config = get_api_key_config()
127
+ if not api_key_config:
128
+ return f"mcp = FastMCP({repr(server_name)}) # No API key authentication configured"
129
+
130
+ generated_code_lines = []
131
+
132
+ # Imports
133
+ generated_code_lines.extend([
134
+ "# API key authentication setup",
135
+ "from golf.auth.helpers import set_api_key",
136
+ "from golf.auth.api_key import get_api_key_config",
137
+ "from starlette.middleware.base import BaseHTTPMiddleware",
138
+ "from starlette.requests import Request",
139
+ "",
140
+ f"mcp = FastMCP({repr(server_name)})",
141
+ "",
142
+ "# Middleware to extract API key from headers",
143
+ "class ApiKeyMiddleware(BaseHTTPMiddleware):",
144
+ " async def dispatch(self, request: Request, call_next):",
145
+ " api_key_config = get_api_key_config()",
146
+ " if api_key_config:",
147
+ " # Extract API key from the configured header",
148
+ " header_name = api_key_config.header_name",
149
+ " header_prefix = api_key_config.header_prefix",
150
+ " ",
151
+ " # Case-insensitive header lookup",
152
+ " api_key = None",
153
+ " for k, v in request.headers.items():",
154
+ " if k.lower() == header_name.lower():",
155
+ " api_key = v",
156
+ " break",
157
+ " ",
158
+ " # Strip prefix if configured and present",
159
+ " if api_key and header_prefix and api_key.startswith(header_prefix):",
160
+ " api_key = api_key[len(header_prefix):]",
161
+ " ",
162
+ " # Store the API key in context for tools to access",
163
+ " set_api_key(api_key)",
164
+ " ",
165
+ " # Continue with the request",
166
+ " response = await call_next(request)",
167
+ " return response",
168
+ "",
169
+ "# Add the middleware to the FastMCP app",
170
+ "mcp.app.add_middleware(ApiKeyMiddleware)",
171
+ ])
172
+
173
+ return "\n".join(generated_code_lines)
174
+
175
+
117
176
  def generate_auth_routes() -> str:
118
177
  """Generate code for OAuth routes in the FastMCP app.
119
178
  These routes are added to the FastMCP instance (`mcp`) created by `generate_auth_code`.
120
179
  """
180
+ # API key auth doesn't need special routes
181
+ api_key_config = get_api_key_config()
182
+ if api_key_config:
183
+ return ""
184
+
121
185
  provider_config, _ = get_auth_config() # Used to check if auth is enabled generally
122
186
  if not provider_config:
123
187
  return ""
@@ -17,7 +17,7 @@ console = Console()
17
17
  # PostHog configuration
18
18
  # This is a client-side API key, safe to be public
19
19
  # Users can override with GOLF_POSTHOG_API_KEY environment variable
20
- DEFAULT_POSTHOG_API_KEY = "phc_7ccsDDxoC5tK5hodlrs2moGC74cThRzcN63flRYPWGl" # Replace with your actual PostHog project API key
20
+ DEFAULT_POSTHOG_API_KEY = "phc_7ccsDDxoC5tK5hodlrs2moGC74cThRzcN63flRYPWGl"
21
21
  POSTHOG_API_KEY = os.environ.get("GOLF_POSTHOG_API_KEY", DEFAULT_POSTHOG_API_KEY)
22
22
  POSTHOG_HOST = "https://us.i.posthog.com"
23
23
 
@@ -156,8 +156,8 @@ def initialize_telemetry() -> None:
156
156
  if not is_telemetry_enabled():
157
157
  return
158
158
 
159
- # Skip initialization if no valid API key
160
- if not POSTHOG_API_KEY or POSTHOG_API_KEY == DEFAULT_POSTHOG_API_KEY:
159
+ # Skip initialization if no valid API key (empty or placeholder)
160
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY.startswith("phc_YOUR"):
161
161
  return
162
162
 
163
163
  try:
@@ -183,8 +183,8 @@ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) ->
183
183
  if not is_telemetry_enabled():
184
184
  return
185
185
 
186
- # Skip if no valid API key
187
- if not POSTHOG_API_KEY or POSTHOG_API_KEY == DEFAULT_POSTHOG_API_KEY:
186
+ # Skip if no valid API key (empty or placeholder)
187
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY.startswith("phc_YOUR"):
188
188
  return
189
189
 
190
190
  try:
@@ -0,0 +1,70 @@
1
+ # GitHub MCP Server with API Key Authentication
2
+
3
+ This example demonstrates how to build a GitHub API MCP server using Golf's API key authentication feature. The server wraps common GitHub operations and passes through authentication tokens from MCP clients to the GitHub API.
4
+
5
+ ## Features
6
+
7
+ This MCP server provides tools for:
8
+
9
+ - **Repository Management**
10
+ - `list-repos` - List repositories for users, organizations, or the authenticated user
11
+
12
+ - **Issue Management**
13
+ - `create-issues` - Create new issues with labels
14
+ - `list-issues` - List and filter issues by state and labels
15
+
16
+ - **Code Search**
17
+ - `code-search` - Search for code across GitHub with language and repository filters
18
+
19
+ - **User Information**
20
+ - `get-users` - Get user profiles or verify authentication
21
+
22
+ ## Tool Naming Convention
23
+
24
+ Golf automatically derives tool names from the file structure:
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
+
31
+ ## Configuration
32
+
33
+ The server is configured in `pre_build.py` to extract GitHub tokens from the `Authorization` header:
34
+
35
+ ```python
36
+ configure_api_key(
37
+ header_name="Authorization",
38
+ header_prefix="Bearer "
39
+ )
40
+ ```
41
+
42
+ This configuration handles GitHub's token format: `Authorization: Bearer ghp_xxxxxxxxxxxx`
43
+
44
+ ## How It Works
45
+
46
+ 1. **Client sends request** with GitHub token in the Authorization header
47
+ 2. **Golf middleware** extracts the token based on your configuration
48
+ 3. **Tools retrieve token** using `get_api_key()`
49
+ 4. **Token is forwarded** to GitHub API in the appropriate format
50
+ 5. **GitHub validates** the token and returns results
51
+
52
+ ## Running the Server
53
+
54
+ 1. Build and run:
55
+ ```bash
56
+ golf build dev
57
+ golf run
58
+ ```
59
+
60
+ 2. The server will start on `http://127.0.0.1:3000` (configurable in `golf.json`)
61
+
62
+
63
+ ## GitHub Token Permissions
64
+
65
+ Depending on which tools you use, you'll need different token permissions:
66
+
67
+ - **Public repositories**: No token needed for read-only access
68
+ - **Private repositories**: Token with `repo` scope
69
+ - **Creating issues**: Token with `repo` or `public_repo` scope
70
+ - **User information**: Token with `user` scope
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "github-mcp-server",
3
+ "description": "MCP server for GitHub API operations with API key authentication",
4
+ "host": "127.0.0.1",
5
+ "port": 3000,
6
+ "transport": "sse"
7
+ }
@@ -0,0 +1,10 @@
1
+ """Configure API key authentication for GitHub MCP server."""
2
+
3
+ from golf.auth import configure_api_key
4
+
5
+ # Configure Golf to extract GitHub personal access tokens from the Authorization header
6
+ # GitHub expects: Authorization: Bearer ghp_xxxx or Authorization: token ghp_xxxx
7
+ configure_api_key(
8
+ header_name="Authorization",
9
+ header_prefix="Bearer " # Will handle both "Bearer " and "token " prefixes
10
+ )
@@ -0,0 +1,94 @@
1
+ """Create a new issue in a GitHub repository."""
2
+
3
+ from typing import Annotated, List, Optional
4
+ from pydantic import BaseModel, Field
5
+ import httpx
6
+
7
+ from golf.auth import get_api_key
8
+
9
+
10
+ class Output(BaseModel):
11
+ """Response from creating an issue."""
12
+ success: bool
13
+ issue_number: Optional[int] = None
14
+ issue_url: Optional[str] = None
15
+ error: Optional[str] = None
16
+
17
+
18
+ async def create(
19
+ repo: Annotated[str, Field(description="Repository name in format 'owner/repo'")],
20
+ title: Annotated[str, Field(description="Issue title")],
21
+ body: Annotated[str, Field(description="Issue description/body (supports Markdown)")] = "",
22
+ labels: Annotated[Optional[List[str]], Field(description="List of label names to apply")] = None
23
+ ) -> Output:
24
+ """Create a new issue.
25
+
26
+ Requires authentication with appropriate permissions.
27
+ """
28
+ github_token = get_api_key()
29
+
30
+ if not github_token:
31
+ return Output(
32
+ success=False,
33
+ error="Authentication required. Please provide a GitHub token."
34
+ )
35
+
36
+ # Validate repo format
37
+ if '/' not in repo:
38
+ return Output(
39
+ success=False,
40
+ error="Repository must be in format 'owner/repo'"
41
+ )
42
+
43
+ url = f"https://api.github.com/repos/{repo}/issues"
44
+
45
+ # Build request payload
46
+ payload = {
47
+ "title": title,
48
+ "body": body
49
+ }
50
+ if labels:
51
+ payload["labels"] = labels
52
+
53
+ try:
54
+ async with httpx.AsyncClient() as client:
55
+ response = await client.post(
56
+ url,
57
+ headers={
58
+ "Authorization": f"Bearer {github_token}",
59
+ "Accept": "application/vnd.github.v3+json",
60
+ "User-Agent": "Golf-GitHub-MCP-Server"
61
+ },
62
+ json=payload,
63
+ timeout=10.0
64
+ )
65
+
66
+ response.raise_for_status()
67
+ issue_data = response.json()
68
+
69
+ return Output(
70
+ success=True,
71
+ issue_number=issue_data["number"],
72
+ issue_url=issue_data["html_url"]
73
+ )
74
+
75
+ except httpx.HTTPStatusError as e:
76
+ error_messages = {
77
+ 401: "Invalid or missing authentication token",
78
+ 403: "Insufficient permissions to create issues in this repository",
79
+ 404: "Repository not found",
80
+ 422: "Invalid request data"
81
+ }
82
+ return Output(
83
+ success=False,
84
+ error=error_messages.get(e.response.status_code, f"GitHub API error: {e.response.status_code}")
85
+ )
86
+ except Exception as e:
87
+ return Output(
88
+ success=False,
89
+ error=f"Failed to create issue: {str(e)}"
90
+ )
91
+
92
+
93
+ # Export the function to be used as the tool
94
+ export = create