golf-mcp 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl
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/__init__.py +1 -1
- golf/auth/__init__.py +2 -1
- golf/auth/api_key.py +73 -0
- golf/auth/helpers.py +45 -4
- golf/cli/main.py +1 -1
- golf/commands/init.py +4 -3
- golf/core/builder.py +10 -1
- golf/core/builder_auth.py +64 -0
- golf/core/telemetry.py +5 -5
- golf/examples/api_key/README.md +70 -0
- golf/examples/api_key/golf.json +7 -0
- golf/examples/api_key/pre_build.py +10 -0
- golf/examples/api_key/tools/issues/create.py +94 -0
- golf/examples/api_key/tools/issues/list.py +87 -0
- golf/examples/api_key/tools/repos/list.py +90 -0
- golf/examples/api_key/tools/search/code.py +93 -0
- golf/examples/api_key/tools/users/get.py +81 -0
- golf/examples/basic/golf.json +1 -3
- golf/examples/basic/tools/hello.py +4 -3
- golf/examples/basic/tools/payments/charge.py +15 -4
- golf/examples/basic/tools/payments/refund.py +19 -12
- {golf_mcp-0.1.4.dist-info → golf_mcp-0.1.6.dist-info}/METADATA +15 -20
- golf_mcp-0.1.6.dist-info/RECORD +51 -0
- golf_mcp-0.1.4.dist-info/RECORD +0 -42
- {golf_mcp-0.1.4.dist-info → golf_mcp-0.1.6.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.4.dist-info → golf_mcp-0.1.6.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.4.dist-info → golf_mcp-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.4.dist-info → golf_mcp-0.1.6.dist-info}/top_level.txt +0 -0
golf/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.6"
|
golf/auth/__init__.py
CHANGED
|
@@ -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."""
|
golf/auth/api_key.py
ADDED
|
@@ -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
|
golf/auth/helpers.py
CHANGED
|
@@ -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()
|
golf/cli/main.py
CHANGED
|
@@ -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.
|
golf/commands/init.py
CHANGED
|
@@ -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
|
|
golf/core/builder.py
CHANGED
|
@@ -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:
|
golf/core/builder_auth.py
CHANGED
|
@@ -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 ""
|
golf/core/telemetry.py
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""List issues in a GitHub repository."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, List, Optional, Dict, Any
|
|
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
|
+
"""List of issues from the repository."""
|
|
12
|
+
issues: List[Dict[str, Any]]
|
|
13
|
+
total_count: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def list(
|
|
17
|
+
repo: Annotated[str, Field(description="Repository name in format 'owner/repo'")],
|
|
18
|
+
state: Annotated[str, Field(description="Filter by state - 'open', 'closed', or 'all'")] = "open",
|
|
19
|
+
labels: Annotated[Optional[str], Field(description="Comma-separated list of label names to filter by")] = None,
|
|
20
|
+
per_page: Annotated[int, Field(description="Number of results per page (max 100)")] = 20
|
|
21
|
+
) -> Output:
|
|
22
|
+
"""List issues in a repository.
|
|
23
|
+
|
|
24
|
+
Returns issues with their number, title, state, and other metadata.
|
|
25
|
+
Pull requests are filtered out from the results.
|
|
26
|
+
"""
|
|
27
|
+
github_token = get_api_key()
|
|
28
|
+
|
|
29
|
+
# Validate repo format
|
|
30
|
+
if '/' not in repo:
|
|
31
|
+
return Output(issues=[], total_count=0)
|
|
32
|
+
|
|
33
|
+
url = f"https://api.github.com/repos/{repo}/issues"
|
|
34
|
+
|
|
35
|
+
# Prepare headers
|
|
36
|
+
headers = {
|
|
37
|
+
"Accept": "application/vnd.github.v3+json",
|
|
38
|
+
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
39
|
+
}
|
|
40
|
+
if github_token:
|
|
41
|
+
headers["Authorization"] = f"Bearer {github_token}"
|
|
42
|
+
|
|
43
|
+
# Build query parameters
|
|
44
|
+
params = {
|
|
45
|
+
"state": state,
|
|
46
|
+
"per_page": min(per_page, 100)
|
|
47
|
+
}
|
|
48
|
+
if labels:
|
|
49
|
+
params["labels"] = labels
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
async with httpx.AsyncClient() as client:
|
|
53
|
+
response = await client.get(
|
|
54
|
+
url,
|
|
55
|
+
headers=headers,
|
|
56
|
+
params=params,
|
|
57
|
+
timeout=10.0
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
response.raise_for_status()
|
|
61
|
+
issues_data = response.json()
|
|
62
|
+
|
|
63
|
+
# Filter out pull requests and format issues
|
|
64
|
+
issues = []
|
|
65
|
+
for issue in issues_data:
|
|
66
|
+
# Skip pull requests (they appear in issues endpoint too)
|
|
67
|
+
if "pull_request" in issue:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
issues.append({
|
|
71
|
+
"number": issue["number"],
|
|
72
|
+
"title": issue["title"],
|
|
73
|
+
"body": issue.get("body", ""),
|
|
74
|
+
"state": issue["state"],
|
|
75
|
+
"url": issue["html_url"],
|
|
76
|
+
"user": issue["user"]["login"],
|
|
77
|
+
"labels": [label["name"] for label in issue.get("labels", [])]
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return Output(issues=issues, total_count=len(issues))
|
|
81
|
+
|
|
82
|
+
except Exception:
|
|
83
|
+
return Output(issues=[], total_count=0)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Export the function to be used as the tool
|
|
87
|
+
export = list
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""List GitHub repositories for a user or organization."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, List, Optional, Dict, Any
|
|
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
|
+
"""List of repositories."""
|
|
12
|
+
repositories: List[Dict[str, Any]]
|
|
13
|
+
total_count: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def list(
|
|
17
|
+
username: Annotated[Optional[str], Field(description="GitHub username (lists public repos, or all repos if authenticated as this user)")] = None,
|
|
18
|
+
org: Annotated[Optional[str], Field(description="GitHub organization name (lists public repos, or all repos if authenticated member)")] = None,
|
|
19
|
+
sort: Annotated[str, Field(description="How to sort results - 'created', 'updated', 'pushed', 'full_name'")] = "updated",
|
|
20
|
+
per_page: Annotated[int, Field(description="Number of results per page (max 100)")] = 20
|
|
21
|
+
) -> Output:
|
|
22
|
+
"""List GitHub repositories.
|
|
23
|
+
|
|
24
|
+
If neither username nor org is provided, lists repositories for the authenticated user.
|
|
25
|
+
"""
|
|
26
|
+
# Get the GitHub token from the request context
|
|
27
|
+
github_token = get_api_key()
|
|
28
|
+
|
|
29
|
+
# Determine the API endpoint
|
|
30
|
+
if org:
|
|
31
|
+
url = f"https://api.github.com/orgs/{org}/repos"
|
|
32
|
+
elif username:
|
|
33
|
+
url = f"https://api.github.com/users/{username}/repos"
|
|
34
|
+
else:
|
|
35
|
+
# List repos for authenticated user
|
|
36
|
+
if not github_token:
|
|
37
|
+
return Output(repositories=[], total_count=0)
|
|
38
|
+
url = "https://api.github.com/user/repos"
|
|
39
|
+
|
|
40
|
+
# Prepare headers
|
|
41
|
+
headers = {
|
|
42
|
+
"Accept": "application/vnd.github.v3+json",
|
|
43
|
+
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
44
|
+
}
|
|
45
|
+
if github_token:
|
|
46
|
+
headers["Authorization"] = f"Bearer {github_token}"
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
async with httpx.AsyncClient() as client:
|
|
50
|
+
response = await client.get(
|
|
51
|
+
url,
|
|
52
|
+
headers=headers,
|
|
53
|
+
params={
|
|
54
|
+
"sort": sort,
|
|
55
|
+
"per_page": min(per_page, 100),
|
|
56
|
+
"type": "all" if not username and not org else None
|
|
57
|
+
},
|
|
58
|
+
timeout=10.0
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
response.raise_for_status()
|
|
62
|
+
repos_data = response.json()
|
|
63
|
+
|
|
64
|
+
# Format repositories
|
|
65
|
+
repositories = []
|
|
66
|
+
for repo in repos_data:
|
|
67
|
+
repositories.append({
|
|
68
|
+
"name": repo["name"],
|
|
69
|
+
"full_name": repo["full_name"],
|
|
70
|
+
"description": repo.get("description", ""),
|
|
71
|
+
"private": repo.get("private", False),
|
|
72
|
+
"stars": repo.get("stargazers_count", 0),
|
|
73
|
+
"forks": repo.get("forks_count", 0),
|
|
74
|
+
"language": repo.get("language", ""),
|
|
75
|
+
"url": repo["html_url"]
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return Output(repositories=repositories, total_count=len(repositories))
|
|
79
|
+
|
|
80
|
+
except httpx.HTTPStatusError as e:
|
|
81
|
+
if e.response.status_code in [401, 404]:
|
|
82
|
+
return Output(repositories=[], total_count=0)
|
|
83
|
+
else:
|
|
84
|
+
return Output(repositories=[], total_count=0)
|
|
85
|
+
except Exception:
|
|
86
|
+
return Output(repositories=[], total_count=0)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Export the function to be used as the tool
|
|
90
|
+
export = list
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Search GitHub code."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, List, Optional, Dict, Any
|
|
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
|
+
"""Code search results."""
|
|
12
|
+
results: List[Dict[str, Any]]
|
|
13
|
+
total_count: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def search(
|
|
17
|
+
query: Annotated[str, Field(description="Search query (e.g., 'addClass', 'TODO', etc.)")],
|
|
18
|
+
language: Annotated[Optional[str], Field(description="Filter by programming language (e.g., 'python', 'javascript')")] = None,
|
|
19
|
+
repo: Annotated[Optional[str], Field(description="Search within a specific repository (format: 'owner/repo')")] = None,
|
|
20
|
+
org: Annotated[Optional[str], Field(description="Search within repositories of a specific organization")] = None,
|
|
21
|
+
per_page: Annotated[int, Field(description="Number of results per page (max 100)")] = 10
|
|
22
|
+
) -> Output:
|
|
23
|
+
"""Search for code on GitHub.
|
|
24
|
+
|
|
25
|
+
Without authentication, you're limited to 10 requests per minute.
|
|
26
|
+
With authentication, you can make up to 30 requests per minute.
|
|
27
|
+
"""
|
|
28
|
+
github_token = get_api_key()
|
|
29
|
+
|
|
30
|
+
# Build the search query
|
|
31
|
+
search_parts = [query]
|
|
32
|
+
if language:
|
|
33
|
+
search_parts.append(f"language:{language}")
|
|
34
|
+
if repo:
|
|
35
|
+
search_parts.append(f"repo:{repo}")
|
|
36
|
+
if org:
|
|
37
|
+
search_parts.append(f"org:{org}")
|
|
38
|
+
|
|
39
|
+
search_query = " ".join(search_parts)
|
|
40
|
+
|
|
41
|
+
url = "https://api.github.com/search/code"
|
|
42
|
+
|
|
43
|
+
# Prepare headers
|
|
44
|
+
headers = {
|
|
45
|
+
"Accept": "application/vnd.github.v3+json",
|
|
46
|
+
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
47
|
+
}
|
|
48
|
+
if github_token:
|
|
49
|
+
headers["Authorization"] = f"Bearer {github_token}"
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
async with httpx.AsyncClient() as client:
|
|
53
|
+
response = await client.get(
|
|
54
|
+
url,
|
|
55
|
+
headers=headers,
|
|
56
|
+
params={
|
|
57
|
+
"q": search_query,
|
|
58
|
+
"per_page": min(per_page, 100)
|
|
59
|
+
},
|
|
60
|
+
timeout=10.0
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if response.status_code == 403:
|
|
64
|
+
# Rate limit exceeded
|
|
65
|
+
return Output(results=[], total_count=0)
|
|
66
|
+
|
|
67
|
+
response.raise_for_status()
|
|
68
|
+
data = response.json()
|
|
69
|
+
|
|
70
|
+
# Format results
|
|
71
|
+
results = []
|
|
72
|
+
for item in data.get("items", []):
|
|
73
|
+
results.append({
|
|
74
|
+
"name": item["name"],
|
|
75
|
+
"path": item["path"],
|
|
76
|
+
"repository": item["repository"]["full_name"],
|
|
77
|
+
"url": item["html_url"],
|
|
78
|
+
"score": item.get("score", 0.0)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return Output(
|
|
82
|
+
results=results,
|
|
83
|
+
total_count=data.get("total_count", 0)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
except httpx.HTTPStatusError:
|
|
87
|
+
return Output(results=[], total_count=0)
|
|
88
|
+
except Exception:
|
|
89
|
+
return Output(results=[], total_count=0)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Export the function to be used as the tool
|
|
93
|
+
export = search
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Get GitHub user information."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Optional, Dict, Any
|
|
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
|
+
"""User information result."""
|
|
12
|
+
found: bool
|
|
13
|
+
user: Optional[Dict[str, Any]] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def get(
|
|
17
|
+
username: Annotated[Optional[str], Field(description="GitHub username (if not provided, gets authenticated user)")] = None
|
|
18
|
+
) -> Output:
|
|
19
|
+
"""Get information about a GitHub user.
|
|
20
|
+
|
|
21
|
+
If no username is provided, returns information about the authenticated user.
|
|
22
|
+
This is useful for testing if authentication is working correctly.
|
|
23
|
+
"""
|
|
24
|
+
github_token = get_api_key()
|
|
25
|
+
|
|
26
|
+
# Determine the API endpoint
|
|
27
|
+
if username:
|
|
28
|
+
url = f"https://api.github.com/users/{username}"
|
|
29
|
+
else:
|
|
30
|
+
# Get authenticated user - requires token
|
|
31
|
+
if not github_token:
|
|
32
|
+
return Output(found=False)
|
|
33
|
+
url = "https://api.github.com/user"
|
|
34
|
+
|
|
35
|
+
# Prepare headers
|
|
36
|
+
headers = {
|
|
37
|
+
"Accept": "application/vnd.github.v3+json",
|
|
38
|
+
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
39
|
+
}
|
|
40
|
+
if github_token:
|
|
41
|
+
headers["Authorization"] = f"Bearer {github_token}"
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
async with httpx.AsyncClient() as client:
|
|
45
|
+
response = await client.get(
|
|
46
|
+
url,
|
|
47
|
+
headers=headers,
|
|
48
|
+
timeout=10.0
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
response.raise_for_status()
|
|
52
|
+
user_data = response.json()
|
|
53
|
+
|
|
54
|
+
return Output(
|
|
55
|
+
found=True,
|
|
56
|
+
user={
|
|
57
|
+
"login": user_data["login"],
|
|
58
|
+
"name": user_data.get("name", ""),
|
|
59
|
+
"email": user_data.get("email", ""),
|
|
60
|
+
"bio": user_data.get("bio", ""),
|
|
61
|
+
"company": user_data.get("company", ""),
|
|
62
|
+
"location": user_data.get("location", ""),
|
|
63
|
+
"public_repos": user_data.get("public_repos", 0),
|
|
64
|
+
"followers": user_data.get("followers", 0),
|
|
65
|
+
"following": user_data.get("following", 0),
|
|
66
|
+
"created_at": user_data["created_at"],
|
|
67
|
+
"url": user_data["html_url"]
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
except httpx.HTTPStatusError as e:
|
|
72
|
+
if e.response.status_code in [401, 404]:
|
|
73
|
+
return Output(found=False)
|
|
74
|
+
else:
|
|
75
|
+
return Output(found=False)
|
|
76
|
+
except Exception:
|
|
77
|
+
return Output(found=False)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Export the function to be used as the tool
|
|
81
|
+
export = get
|
golf/examples/basic/golf.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Hello World tool {{project_name}}."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class Output(BaseModel):
|
|
@@ -10,8 +11,8 @@ class Output(BaseModel):
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
async def hello(
|
|
13
|
-
name: str = "World",
|
|
14
|
-
greeting: str = "Hello"
|
|
14
|
+
name: Annotated[str, Field(description="The name of the person to greet")] = "World",
|
|
15
|
+
greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
|
|
15
16
|
) -> Output:
|
|
16
17
|
"""Say hello to the given name.
|
|
17
18
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Charge payment tool"""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
4
5
|
from .common import payment_client
|
|
5
6
|
|
|
6
7
|
|
|
@@ -13,9 +14,19 @@ class Output(BaseModel):
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
async def charge(
|
|
16
|
-
amount: float,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
amount: Annotated[float, Field(
|
|
18
|
+
description="Amount to charge in USD",
|
|
19
|
+
gt=0, # Must be greater than 0
|
|
20
|
+
le=10000 # Maximum charge limit
|
|
21
|
+
)],
|
|
22
|
+
card_token: Annotated[str, Field(
|
|
23
|
+
description="Tokenized payment card identifier",
|
|
24
|
+
pattern=r"^tok_[a-zA-Z0-9]+$" # Validate token format
|
|
25
|
+
)],
|
|
26
|
+
description: Annotated[str, Field(
|
|
27
|
+
description="Optional payment description for the charge",
|
|
28
|
+
max_length=200
|
|
29
|
+
)] = ""
|
|
19
30
|
) -> Output:
|
|
20
31
|
"""Process a payment charge.
|
|
21
32
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Refund payment tool"""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from typing import Annotated, Optional
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
4
5
|
from .common import payment_client
|
|
5
6
|
|
|
6
7
|
|
|
@@ -13,9 +14,20 @@ class Output(BaseModel):
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
async def refund(
|
|
16
|
-
charge_id: str,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
charge_id: Annotated[str, Field(
|
|
18
|
+
description="The ID of the charge to refund",
|
|
19
|
+
pattern=r"^ch_[a-zA-Z0-9]+$"
|
|
20
|
+
)],
|
|
21
|
+
amount: Annotated[Optional[float], Field(
|
|
22
|
+
description="Amount to refund in USD. If not specified, refunds the full charge amount",
|
|
23
|
+
gt=0,
|
|
24
|
+
default=None
|
|
25
|
+
)] = None,
|
|
26
|
+
reason: Annotated[str, Field(
|
|
27
|
+
description="Reason for the refund",
|
|
28
|
+
min_length=3,
|
|
29
|
+
max_length=200
|
|
30
|
+
)] = "Customer request"
|
|
19
31
|
) -> Output:
|
|
20
32
|
"""Process a payment refund.
|
|
21
33
|
|
|
@@ -23,15 +35,10 @@ async def refund(
|
|
|
23
35
|
are grouped in subdirectories (tools/payments/refund.py).
|
|
24
36
|
|
|
25
37
|
The resulting tool ID will be: refund-payments
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
charge_id: Original charge ID to refund
|
|
29
|
-
amount: Amount to refund in USD
|
|
30
|
-
reason: Reason for refund
|
|
31
38
|
"""
|
|
32
39
|
# The framework will add a context object automatically
|
|
33
40
|
# You can log using regular print during development
|
|
34
|
-
print(f"Processing refund
|
|
41
|
+
print(f"Processing refund for charge {charge_id}...")
|
|
35
42
|
|
|
36
43
|
# Use the shared payment client from common.py
|
|
37
44
|
refund_result = await payment_client.create_refund(
|
|
@@ -44,7 +51,7 @@ async def refund(
|
|
|
44
51
|
return Output(
|
|
45
52
|
success=True,
|
|
46
53
|
refund_id=refund_result["id"],
|
|
47
|
-
message=f"Successfully refunded
|
|
48
|
-
)
|
|
54
|
+
message=f"Successfully refunded charge {charge_id}"
|
|
55
|
+
)
|
|
49
56
|
|
|
50
57
|
export = refund
|
|
@@ -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>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
golf/__init__.py,sha256=l1lWRV5fzx6-C3xpWdEYCx3Y5TNykf_HgoEs12q6cfQ,21
|
|
2
|
+
golf/auth/__init__.py,sha256=nh22WdganrEtp0YnxX1dmk6euuUFu8zh1BWxi1LGZnI,4107
|
|
3
|
+
golf/auth/api_key.py,sha256=5UKmWZTs7sQ6bZ8baJjd-IITZNBwiExES0pVpq0-DEs,2049
|
|
4
|
+
golf/auth/helpers.py,sha256=eKUIhdsaxa9bBtU6BaVZszTtS1ZV1v1fzvCkXV__9pM,2965
|
|
5
|
+
golf/auth/oauth.py,sha256=MGtuKMSt77yihuZiuiEm33xAlXJynIMcFmMLZRlppdg,31525
|
|
6
|
+
golf/auth/provider.py,sha256=-iIjw1XruxgpWTrwYBX9dbf_sLPlx5vZxZsM0E4Dp64,3931
|
|
7
|
+
golf/cli/__init__.py,sha256=MG53_GB5QcXHJAwSUKhy3xWLWwzc3FrnFbcCg4z6M0w,45
|
|
8
|
+
golf/cli/main.py,sha256=yCgBe9aWKwBmPGOLDPFlkwKKc0U7_ULDK7Y8_WytAow,9835
|
|
9
|
+
golf/commands/__init__.py,sha256=FRehcdCr0TrH_399w-PO9OzLhE7z2FVoCFAmfqHsYWo,83
|
|
10
|
+
golf/commands/build.py,sha256=yhcUrK9gHnXNuLPZ2brcmtMgR8wXMBlEBcaBgTKkanI,2295
|
|
11
|
+
golf/commands/init.py,sha256=I-fe7PBDX0vfKCLGXMppD7Uo03LzRbx-bCM2Mt3UzdU,7155
|
|
12
|
+
golf/commands/run.py,sha256=dLrjmpypfvInDkA-KgEUyKrTvkvp59yea5tUyxk_Ej4,1989
|
|
13
|
+
golf/core/__init__.py,sha256=RDSMAkB5NDaV7W5OFJ--miqv6JmvM3rI9ashndqM3X4,52
|
|
14
|
+
golf/core/builder.py,sha256=SXJ4f4zAPlVtzreq9sQd0fMPWlLy8WkAUdfPY-aqG78,52798
|
|
15
|
+
golf/core/builder_auth.py,sha256=y7cVqwbOqvhBOU67xCwtV5L-85b1E_Q3Khu91ilpUpU,11800
|
|
16
|
+
golf/core/builder_telemetry.py,sha256=6ip4peRcb-RVZ6djorKW_VW6IiTCbnzOJYQm_YOMvpc,10189
|
|
17
|
+
golf/core/config.py,sha256=gZ3eE8VIvDgIrLTizNq9XqkgfMkRg8Pm06gSVV51arA,6806
|
|
18
|
+
golf/core/parser.py,sha256=sCVMWO7sGkSaKkU9LiHC3O9CTcrZxUbxnjdtwP7ne_o,19251
|
|
19
|
+
golf/core/telemetry.py,sha256=mjkr6VaHABiu1SLyaFG6T8EfdvWjyNZVWY6FEWp-WHU,7385
|
|
20
|
+
golf/core/transformer.py,sha256=3mr4_K4l1HZl1WQWSt9NKEhB5oi3b7kJnniseYEfrcI,5573
|
|
21
|
+
golf/examples/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
|
22
|
+
golf/examples/api_key/README.md,sha256=DIVcGwfs4FVXpk9R5gz1R-AvkzcLlNCZYEPTKlvWjCg,2235
|
|
23
|
+
golf/examples/api_key/golf.json,sha256=iBSQWsazl6k7xdg1GhSJkzx4dANDjUp0WGFrI8M9Bug,180
|
|
24
|
+
golf/examples/api_key/pre_build.py,sha256=N7q77CZwLnL8mgaPI6hXonjD-KX7RveaNMOHhzJ5EqY,407
|
|
25
|
+
golf/examples/api_key/tools/issues/create.py,sha256=b58IadolStcuRxpRdTC_78Kfa4KCvMJSMvAlCouz-Y0,2832
|
|
26
|
+
golf/examples/api_key/tools/issues/list.py,sha256=k2TGEgVCCTs0flHQFRqKQIA2zL39JTlTiUgAymW2gVg,2777
|
|
27
|
+
golf/examples/api_key/tools/repos/list.py,sha256=4Z9ISphjhSZdJ3xqxmwbSo7UvkT368ZiSdyckOZqZnQ,3272
|
|
28
|
+
golf/examples/api_key/tools/search/code.py,sha256=eXpJAmKq0am4oiCZm8vfz9d5NmyxjpnMPcEwrCf8vNo,3025
|
|
29
|
+
golf/examples/api_key/tools/users/get.py,sha256=ifzqLxUr0dX00xscyjwuXCB3n48znu28DQU8MGUor50,2577
|
|
30
|
+
golf/examples/basic/.env,sha256=CRh4u1yXPCbjjgUKUMUgw51aOBdVFEgY748z7WgfkVc,156
|
|
31
|
+
golf/examples/basic/.env.example,sha256=gCTFcabTvr_-Rg3kTGmi63JQtCEOwMu4U5jTa1GM66U,123
|
|
32
|
+
golf/examples/basic/README.md,sha256=-mY3R6AAnkXT9FPkALDrJtdf9IyKDvuqjsrLAMTLRYI,2663
|
|
33
|
+
golf/examples/basic/golf.json,sha256=IcorojR67GaOZRrOj_XB6gyRXha7Jrwyc8IHtJM13DU,132
|
|
34
|
+
golf/examples/basic/pre_build.py,sha256=VVYtBSCy4Xjg1Ocpl8ZwEr7VCVqPSSkDEzrTfQkAQKY,1037
|
|
35
|
+
golf/examples/basic/prompts/welcome.py,sha256=LgT1eQewe0J08I5WFBv4RrgDr6yn4a4ww6XQ8k3sTVk,822
|
|
36
|
+
golf/examples/basic/resources/current_time.py,sha256=KG28G5tpFGtGH2T5DHFAc-CwbYsFzE115xmGMVpi7fs,1176
|
|
37
|
+
golf/examples/basic/resources/info.py,sha256=w4eLMapn9M4TN2dvXga5fudAnesFIyst-WgImxeApRQ,735
|
|
38
|
+
golf/examples/basic/resources/weather/common.py,sha256=fmbeUm8DNSg4X48G9krPuv9WK7q42LRvbR4FTn2H3PU,1749
|
|
39
|
+
golf/examples/basic/resources/weather/current.py,sha256=YC3QW5PQxFEWlEQid3pkowrhyqTF0coQDBj7V-M-WeQ,943
|
|
40
|
+
golf/examples/basic/resources/weather/forecast.py,sha256=FpYmgUHjj8bDeWdaX0Lph_XE0QnXOOcI6nwMwB3zhBo,1000
|
|
41
|
+
golf/examples/basic/tools/github_user.py,sha256=hLJ9C4ase7i8nUetLWvEVni8EOvsXZS0Ilhr-Ec0lcw,1975
|
|
42
|
+
golf/examples/basic/tools/hello.py,sha256=wJjCYtWfRpTAcNKrlrRibm0UFKLnHFB1sGjR-q90GGk,867
|
|
43
|
+
golf/examples/basic/tools/payments/charge.py,sha256=BxgHtVfZA3Z_QhT3OJisKyfGr3ZWBzd-jlgdwkvZaYM,1736
|
|
44
|
+
golf/examples/basic/tools/payments/common.py,sha256=z5shSOZizUEnxBmGUSYghxmNwIvuhg-yfXKIfPWgV04,1292
|
|
45
|
+
golf/examples/basic/tools/payments/refund.py,sha256=3auhKzuwPLgpoyUDz0p8lUWUY_fXNe0RYadQBxEvpB4,1603
|
|
46
|
+
golf_mcp-0.1.6.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
|
|
47
|
+
golf_mcp-0.1.6.dist-info/METADATA,sha256=8hibJN6yAw3457LxWZFwHSCIP95eTgQ2GfoWMO9GdRo,9442
|
|
48
|
+
golf_mcp-0.1.6.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
49
|
+
golf_mcp-0.1.6.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
|
|
50
|
+
golf_mcp-0.1.6.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
|
|
51
|
+
golf_mcp-0.1.6.dist-info/RECORD,,
|
golf_mcp-0.1.4.dist-info/RECORD
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
golf/__init__.py,sha256=JMD28FXYHc_TM03visyUSd3UA9FZAaJMRStnfZoq50Y,21
|
|
2
|
-
golf/auth/__init__.py,sha256=0cfMJsLsOAsA4gHynyPGDkrjB90s3xsGSFoCEWg-y6o,3999
|
|
3
|
-
golf/auth/helpers.py,sha256=TqMlOzwtcQcrZ6en2WVkzH0TSQ0RsoDRObpi_rR0Ie4,1681
|
|
4
|
-
golf/auth/oauth.py,sha256=MGtuKMSt77yihuZiuiEm33xAlXJynIMcFmMLZRlppdg,31525
|
|
5
|
-
golf/auth/provider.py,sha256=-iIjw1XruxgpWTrwYBX9dbf_sLPlx5vZxZsM0E4Dp64,3931
|
|
6
|
-
golf/cli/__init__.py,sha256=MG53_GB5QcXHJAwSUKhy3xWLWwzc3FrnFbcCg4z6M0w,45
|
|
7
|
-
golf/cli/main.py,sha256=TcFTqFAcFOl2SxPxHFc6jg8OLVtr92KhVZzabpJISNI,9836
|
|
8
|
-
golf/commands/__init__.py,sha256=FRehcdCr0TrH_399w-PO9OzLhE7z2FVoCFAmfqHsYWo,83
|
|
9
|
-
golf/commands/build.py,sha256=yhcUrK9gHnXNuLPZ2brcmtMgR8wXMBlEBcaBgTKkanI,2295
|
|
10
|
-
golf/commands/init.py,sha256=6LMsjYUGOLrp6AVFx6c1WBFe_qE4W-26q4eMDF8nQAM,7105
|
|
11
|
-
golf/commands/run.py,sha256=dLrjmpypfvInDkA-KgEUyKrTvkvp59yea5tUyxk_Ej4,1989
|
|
12
|
-
golf/core/__init__.py,sha256=RDSMAkB5NDaV7W5OFJ--miqv6JmvM3rI9ashndqM3X4,52
|
|
13
|
-
golf/core/builder.py,sha256=fUHLAjCxroSN_XJUYT07oC4VGvYMKyrzulDyR_VrwhY,52403
|
|
14
|
-
golf/core/builder_auth.py,sha256=epc_pYcKcSaphcbJD-DHnFWFjjvQR6EhPETRfVzN4K4,9174
|
|
15
|
-
golf/core/builder_telemetry.py,sha256=6ip4peRcb-RVZ6djorKW_VW6IiTCbnzOJYQm_YOMvpc,10189
|
|
16
|
-
golf/core/config.py,sha256=gZ3eE8VIvDgIrLTizNq9XqkgfMkRg8Pm06gSVV51arA,6806
|
|
17
|
-
golf/core/parser.py,sha256=sCVMWO7sGkSaKkU9LiHC3O9CTcrZxUbxnjdtwP7ne_o,19251
|
|
18
|
-
golf/core/telemetry.py,sha256=KMp75b3hzHmE0PBcCZdsCihRi5c3zB8p6M16flY2As4,7399
|
|
19
|
-
golf/core/transformer.py,sha256=3mr4_K4l1HZl1WQWSt9NKEhB5oi3b7kJnniseYEfrcI,5573
|
|
20
|
-
golf/examples/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
|
21
|
-
golf/examples/basic/.env,sha256=CRh4u1yXPCbjjgUKUMUgw51aOBdVFEgY748z7WgfkVc,156
|
|
22
|
-
golf/examples/basic/.env.example,sha256=gCTFcabTvr_-Rg3kTGmi63JQtCEOwMu4U5jTa1GM66U,123
|
|
23
|
-
golf/examples/basic/README.md,sha256=-mY3R6AAnkXT9FPkALDrJtdf9IyKDvuqjsrLAMTLRYI,2663
|
|
24
|
-
golf/examples/basic/golf.json,sha256=JThi6EC93trGqECo3ryD3MhDcyR9Haub8kYvmYo4yjQ,213
|
|
25
|
-
golf/examples/basic/pre_build.py,sha256=VVYtBSCy4Xjg1Ocpl8ZwEr7VCVqPSSkDEzrTfQkAQKY,1037
|
|
26
|
-
golf/examples/basic/prompts/welcome.py,sha256=LgT1eQewe0J08I5WFBv4RrgDr6yn4a4ww6XQ8k3sTVk,822
|
|
27
|
-
golf/examples/basic/resources/current_time.py,sha256=KG28G5tpFGtGH2T5DHFAc-CwbYsFzE115xmGMVpi7fs,1176
|
|
28
|
-
golf/examples/basic/resources/info.py,sha256=w4eLMapn9M4TN2dvXga5fudAnesFIyst-WgImxeApRQ,735
|
|
29
|
-
golf/examples/basic/resources/weather/common.py,sha256=fmbeUm8DNSg4X48G9krPuv9WK7q42LRvbR4FTn2H3PU,1749
|
|
30
|
-
golf/examples/basic/resources/weather/current.py,sha256=YC3QW5PQxFEWlEQid3pkowrhyqTF0coQDBj7V-M-WeQ,943
|
|
31
|
-
golf/examples/basic/resources/weather/forecast.py,sha256=FpYmgUHjj8bDeWdaX0Lph_XE0QnXOOcI6nwMwB3zhBo,1000
|
|
32
|
-
golf/examples/basic/tools/github_user.py,sha256=hLJ9C4ase7i8nUetLWvEVni8EOvsXZS0Ilhr-Ec0lcw,1975
|
|
33
|
-
golf/examples/basic/tools/hello.py,sha256=hJUMraxgi5g0gzhPbUZGOeAbiKZX4BTr1L64SE8ZZlw,706
|
|
34
|
-
golf/examples/basic/tools/payments/charge.py,sha256=jLOyQmbpRm8cOIK2JXSirPmQGXDM-yHvg7ruv72j6qU,1287
|
|
35
|
-
golf/examples/basic/tools/payments/common.py,sha256=z5shSOZizUEnxBmGUSYghxmNwIvuhg-yfXKIfPWgV04,1292
|
|
36
|
-
golf/examples/basic/tools/payments/refund.py,sha256=_HG4dDeki8FyKCtWs5fXYeLpUsrc_SAx3vxAIS7Icf0,1302
|
|
37
|
-
golf_mcp-0.1.4.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
|
|
38
|
-
golf_mcp-0.1.4.dist-info/METADATA,sha256=wcqXjcA6kPuNASE-OFzcLtvt9_g3S71mhqKfXZv5RFU,9390
|
|
39
|
-
golf_mcp-0.1.4.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
40
|
-
golf_mcp-0.1.4.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
|
|
41
|
-
golf_mcp-0.1.4.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
|
|
42
|
-
golf_mcp-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|