golf-mcp 0.1.6__tar.gz → 0.1.7__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.6/src/golf_mcp.egg-info → golf_mcp-0.1.7}/PKG-INFO +1 -1
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/pyproject.toml +2 -2
- golf_mcp-0.1.7/src/golf/__init__.py +1 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/auth/api_key.py +19 -4
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/builder_auth.py +10 -0
- golf_mcp-0.1.7/src/golf/examples/api_key/.env +2 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/README.md +21 -7
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/pre_build.py +2 -1
- golf_mcp-0.1.7/src/golf/examples/basic/.env +5 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7/src/golf_mcp.egg-info}/PKG-INFO +1 -1
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf_mcp.egg-info/SOURCES.txt +1 -0
- golf_mcp-0.1.6/src/golf/__init__.py +0 -1
- golf_mcp-0.1.6/src/golf/examples/basic/.env +0 -3
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/.docs/docs.md +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/.docs/fast-mcp.md +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/.docs/fastmcp-example-1.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/.docs/fastmcp-example-2.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/.docs/mcp.md +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/.docs/oauth-implementation.md +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/.docs/oauth.md +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/LICENSE +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/MANIFEST.in +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/README.md +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/setup.cfg +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/auth/__init__.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/auth/helpers.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/auth/oauth.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/auth/provider.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/cli/main.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/commands/init.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/commands/run.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/builder.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/config.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/parser.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/telemetry.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/core/transformer.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/golf.json +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/tools/issues/create.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/tools/issues/list.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/tools/repos/list.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/tools/search/code.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/api_key/tools/users/get.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/.env.example +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/golf.json +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/pre_build.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/resources/weather/common.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/tools/github_user.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/tools/hello.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/tools/payments/charge.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/tools/payments/common.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf/examples/basic/tools/payments/refund.py +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf_mcp.egg-info/requires.txt +0 -0
- {golf_mcp-0.1.6 → golf_mcp-0.1.7}/src/golf_mcp.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "golf-mcp"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
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.7"
|
|
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.7"
|
|
@@ -20,6 +20,10 @@ class ApiKeyConfig(BaseModel):
|
|
|
20
20
|
"",
|
|
21
21
|
description="Optional prefix to strip from the header value (e.g., 'Bearer ')"
|
|
22
22
|
)
|
|
23
|
+
required: bool = Field(
|
|
24
|
+
True,
|
|
25
|
+
description="Whether API key is required for all requests"
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
# Global configuration storage
|
|
@@ -28,7 +32,8 @@ _api_key_config: Optional[ApiKeyConfig] = None
|
|
|
28
32
|
|
|
29
33
|
def configure_api_key(
|
|
30
34
|
header_name: str = "X-API-Key",
|
|
31
|
-
header_prefix: str = ""
|
|
35
|
+
header_prefix: str = "",
|
|
36
|
+
required: bool = True
|
|
32
37
|
) -> None:
|
|
33
38
|
"""Configure API key extraction from request headers.
|
|
34
39
|
|
|
@@ -37,21 +42,31 @@ def configure_api_key(
|
|
|
37
42
|
Args:
|
|
38
43
|
header_name: Name of the header containing the API key (default: "X-API-Key")
|
|
39
44
|
header_prefix: Optional prefix to strip from the header value (e.g., "Bearer ")
|
|
40
|
-
|
|
45
|
+
required: Whether API key is required for all requests (default: True)
|
|
41
46
|
|
|
42
47
|
Example:
|
|
43
48
|
# In pre_build.py
|
|
44
49
|
from golf.auth.api_key import configure_api_key
|
|
45
50
|
|
|
51
|
+
# Require API key for all requests
|
|
52
|
+
configure_api_key(
|
|
53
|
+
header_name="Authorization",
|
|
54
|
+
header_prefix="Bearer ",
|
|
55
|
+
required=True
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Or make API key optional (pass-through mode)
|
|
46
59
|
configure_api_key(
|
|
47
60
|
header_name="Authorization",
|
|
48
|
-
header_prefix="Bearer "
|
|
61
|
+
header_prefix="Bearer ",
|
|
62
|
+
required=False
|
|
49
63
|
)
|
|
50
64
|
"""
|
|
51
65
|
global _api_key_config
|
|
52
66
|
_api_key_config = ApiKeyConfig(
|
|
53
67
|
header_name=header_name,
|
|
54
|
-
header_prefix=header_prefix
|
|
68
|
+
header_prefix=header_prefix,
|
|
69
|
+
required=required
|
|
55
70
|
)
|
|
56
71
|
|
|
57
72
|
|
|
@@ -136,6 +136,7 @@ def generate_api_key_auth_code(server_name: str) -> str:
|
|
|
136
136
|
"from golf.auth.api_key import get_api_key_config",
|
|
137
137
|
"from starlette.middleware.base import BaseHTTPMiddleware",
|
|
138
138
|
"from starlette.requests import Request",
|
|
139
|
+
"from starlette.responses import JSONResponse",
|
|
139
140
|
"",
|
|
140
141
|
f"mcp = FastMCP({repr(server_name)})",
|
|
141
142
|
"",
|
|
@@ -147,6 +148,7 @@ def generate_api_key_auth_code(server_name: str) -> str:
|
|
|
147
148
|
" # Extract API key from the configured header",
|
|
148
149
|
" header_name = api_key_config.header_name",
|
|
149
150
|
" header_prefix = api_key_config.header_prefix",
|
|
151
|
+
" is_required = api_key_config.required",
|
|
150
152
|
" ",
|
|
151
153
|
" # Case-insensitive header lookup",
|
|
152
154
|
" api_key = None",
|
|
@@ -159,6 +161,14 @@ def generate_api_key_auth_code(server_name: str) -> str:
|
|
|
159
161
|
" if api_key and header_prefix and api_key.startswith(header_prefix):",
|
|
160
162
|
" api_key = api_key[len(header_prefix):]",
|
|
161
163
|
" ",
|
|
164
|
+
" # Check if API key is required but not provided",
|
|
165
|
+
" if is_required and not api_key:",
|
|
166
|
+
" return JSONResponse(",
|
|
167
|
+
" {\"error\": \"API key required\"},",
|
|
168
|
+
" status_code=401,",
|
|
169
|
+
f" headers={{\"WWW-Authenticate\": f'{{header_name}} realm=\"API Key Required\"'}}",
|
|
170
|
+
" )",
|
|
171
|
+
" ",
|
|
162
172
|
" # Store the API key in context for tools to access",
|
|
163
173
|
" set_api_key(api_key)",
|
|
164
174
|
" ",
|
|
@@ -35,30 +35,44 @@ The server is configured in `pre_build.py` to extract GitHub tokens from the `Au
|
|
|
35
35
|
```python
|
|
36
36
|
configure_api_key(
|
|
37
37
|
header_name="Authorization",
|
|
38
|
-
header_prefix="Bearer "
|
|
38
|
+
header_prefix="Bearer ",
|
|
39
|
+
required=True # Reject requests without a valid API key
|
|
39
40
|
)
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
This configuration
|
|
43
|
+
This configuration:
|
|
44
|
+
- Handles GitHub's token format: `Authorization: Bearer ghp_xxxxxxxxxxxx`
|
|
45
|
+
- **Enforces authentication**: When `required=True` (default), requests without a valid API key will be rejected with a 401 Unauthorized error
|
|
46
|
+
- For optional authentication (pass-through mode), set `required=False`
|
|
43
47
|
|
|
44
48
|
## How It Works
|
|
45
49
|
|
|
46
50
|
1. **Client sends request** with GitHub token in the Authorization header
|
|
47
|
-
2. **Golf middleware**
|
|
48
|
-
3. **
|
|
49
|
-
4. **
|
|
50
|
-
5. **
|
|
51
|
+
2. **Golf middleware** checks if API key is required and present
|
|
52
|
+
3. **If required and missing**, the request is rejected with 401 Unauthorized
|
|
53
|
+
4. **If present**, the token is extracted based on your configuration
|
|
54
|
+
5. **Tools retrieve token** using `get_api_key()`
|
|
55
|
+
6. **Token is forwarded** to GitHub API in the appropriate format
|
|
56
|
+
7. **GitHub validates** the token and returns results
|
|
51
57
|
|
|
52
58
|
## Running the Server
|
|
53
59
|
|
|
54
60
|
1. Build and run:
|
|
55
61
|
```bash
|
|
56
|
-
golf build
|
|
62
|
+
golf build
|
|
57
63
|
golf run
|
|
58
64
|
```
|
|
59
65
|
|
|
60
66
|
2. The server will start on `http://127.0.0.1:3000` (configurable in `golf.json`)
|
|
61
67
|
|
|
68
|
+
3. Test authentication enforcement:
|
|
69
|
+
```bash
|
|
70
|
+
# This will fail with 401 Unauthorized
|
|
71
|
+
curl http://localhost:3000/mcp
|
|
72
|
+
|
|
73
|
+
# This will succeed
|
|
74
|
+
curl -H "Authorization: Bearer ghp_your_token_here" http://localhost:3000/mcp
|
|
75
|
+
```
|
|
62
76
|
|
|
63
77
|
## GitHub Token Permissions
|
|
64
78
|
|
|
@@ -6,5 +6,6 @@ from golf.auth import configure_api_key
|
|
|
6
6
|
# GitHub expects: Authorization: Bearer ghp_xxxx or Authorization: token ghp_xxxx
|
|
7
7
|
configure_api_key(
|
|
8
8
|
header_name="Authorization",
|
|
9
|
-
header_prefix="Bearer " # Will handle both "Bearer " and "token " prefixes
|
|
9
|
+
header_prefix="Bearer ", # Will handle both "Bearer " and "token " prefixes
|
|
10
|
+
required=True # Reject requests without a valid API key
|
|
10
11
|
)
|
|
@@ -30,6 +30,7 @@ src/golf/core/parser.py
|
|
|
30
30
|
src/golf/core/telemetry.py
|
|
31
31
|
src/golf/core/transformer.py
|
|
32
32
|
src/golf/examples/__init__.py
|
|
33
|
+
src/golf/examples/api_key/.env
|
|
33
34
|
src/golf/examples/api_key/README.md
|
|
34
35
|
src/golf/examples/api_key/golf.json
|
|
35
36
|
src/golf/examples/api_key/pre_build.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|