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.
- {golf_mcp-0.1.4/src/golf_mcp.egg-info → golf_mcp-0.1.6}/PKG-INFO +15 -20
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/README.md +14 -19
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/pyproject.toml +2 -2
- golf_mcp-0.1.6/src/golf/__init__.py +1 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/__init__.py +2 -1
- golf_mcp-0.1.6/src/golf/auth/api_key.py +73 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/helpers.py +45 -4
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/cli/main.py +1 -1
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/init.py +4 -3
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/builder.py +10 -1
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/builder_auth.py +64 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/telemetry.py +5 -5
- golf_mcp-0.1.6/src/golf/examples/api_key/README.md +70 -0
- golf_mcp-0.1.6/src/golf/examples/api_key/golf.json +7 -0
- golf_mcp-0.1.6/src/golf/examples/api_key/pre_build.py +10 -0
- golf_mcp-0.1.6/src/golf/examples/api_key/tools/issues/create.py +94 -0
- golf_mcp-0.1.6/src/golf/examples/api_key/tools/issues/list.py +87 -0
- golf_mcp-0.1.6/src/golf/examples/api_key/tools/repos/list.py +90 -0
- golf_mcp-0.1.6/src/golf/examples/api_key/tools/search/code.py +93 -0
- golf_mcp-0.1.6/src/golf/examples/api_key/tools/users/get.py +81 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/golf.json +1 -3
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/hello.py +4 -3
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/payments/charge.py +15 -4
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/payments/refund.py +19 -12
- {golf_mcp-0.1.4 → golf_mcp-0.1.6/src/golf_mcp.egg-info}/PKG-INFO +15 -20
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/SOURCES.txt +9 -0
- golf_mcp-0.1.4/src/golf/__init__.py +0 -1
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/docs.md +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/fast-mcp.md +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/fastmcp-example-1.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/fastmcp-example-2.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/mcp.md +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/oauth-implementation.md +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/.docs/oauth.md +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/LICENSE +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/MANIFEST.in +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/setup.cfg +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/oauth.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/auth/provider.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/commands/run.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/config.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/parser.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/core/transformer.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/.env +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/.env.example +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/pre_build.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/weather/common.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/github_user.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf/examples/basic/tools/payments/common.py +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.1.4 → golf_mcp-0.1.6}/src/golf_mcp.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
9
|
+
from .oauth import GolfOAuthProvider
|
|
9
10
|
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
27
|
+
template: Template to use (basic or api_key)
|
|
28
28
|
"""
|
|
29
29
|
# Validate template
|
|
30
|
-
|
|
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:
|
|
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"
|
|
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
|
|
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
|
|
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,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
|