golf-mcp 0.1.5__py3-none-any.whl → 0.1.7__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.5"
1
+ __version__ = "0.1.7"
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,88 @@
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
+ required: bool = Field(
24
+ True,
25
+ description="Whether API key is required for all requests"
26
+ )
27
+
28
+
29
+ # Global configuration storage
30
+ _api_key_config: Optional[ApiKeyConfig] = None
31
+
32
+
33
+ def configure_api_key(
34
+ header_name: str = "X-API-Key",
35
+ header_prefix: str = "",
36
+ required: bool = True
37
+ ) -> None:
38
+ """Configure API key extraction from request headers.
39
+
40
+ This function should be called in pre_build.py to set up API key handling.
41
+
42
+ Args:
43
+ header_name: Name of the header containing the API key (default: "X-API-Key")
44
+ header_prefix: Optional prefix to strip from the header value (e.g., "Bearer ")
45
+ required: Whether API key is required for all requests (default: True)
46
+
47
+ Example:
48
+ # In pre_build.py
49
+ from golf.auth.api_key import configure_api_key
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)
59
+ configure_api_key(
60
+ header_name="Authorization",
61
+ header_prefix="Bearer ",
62
+ required=False
63
+ )
64
+ """
65
+ global _api_key_config
66
+ _api_key_config = ApiKeyConfig(
67
+ header_name=header_name,
68
+ header_prefix=header_prefix,
69
+ required=required
70
+ )
71
+
72
+
73
+ def get_api_key_config() -> Optional[ApiKeyConfig]:
74
+ """Get the current API key configuration.
75
+
76
+ Returns:
77
+ The API key configuration if set, None otherwise
78
+ """
79
+ return _api_key_config
80
+
81
+
82
+ def is_api_key_configured() -> bool:
83
+ """Check if API key authentication is configured.
84
+
85
+ Returns:
86
+ True if API key authentication is configured, False otherwise
87
+ """
88
+ 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
- _active_golf_oauth_provider = None
9
+ from .oauth import GolfOAuthProvider
9
10
 
10
- def _set_active_golf_oauth_provider(provider_instance) -> None:
11
+ # Context variable to store the active OAuth provider
12
+ _active_golf_oauth_provider: Optional[GolfOAuthProvider] = None
13
+
14
+ # Context variable to store the current request's API key
15
+ _current_api_key: ContextVar[Optional[str]] = ContextVar('current_api_key', default=None)
16
+
17
+ def _set_active_golf_oauth_provider(provider: GolfOAuthProvider) -> None:
11
18
  """
12
19
  Sets the active GolfOAuthProvider instance.
13
20
  Should only be called once during server startup.
14
21
  """
15
22
  global _active_golf_oauth_provider
16
- _active_golf_oauth_provider = provider_instance
23
+ _active_golf_oauth_provider = provider
17
24
 
18
25
  def get_provider_token() -> Optional[str]:
19
26
  """
@@ -53,4 +60,38 @@ def extract_token_from_header(auth_header: str) -> Optional[str]:
53
60
  if len(parts) != 2 or parts[0].lower() != 'bearer':
54
61
  return None
55
62
 
56
- return parts[1]
63
+ return parts[1]
64
+
65
+ def set_api_key(api_key: Optional[str]) -> None:
66
+ """Set the API key for the current request context.
67
+
68
+ This is an internal function used by the middleware.
69
+
70
+ Args:
71
+ api_key: The API key to store in the context
72
+ """
73
+ _current_api_key.set(api_key)
74
+
75
+ def get_api_key() -> Optional[str]:
76
+ """Get the API key from the current request context.
77
+
78
+ This function should be used in tools to retrieve the API key
79
+ that was sent in the request headers.
80
+
81
+ Returns:
82
+ The API key if available, None otherwise
83
+
84
+ Example:
85
+ # In a tool file
86
+ from golf.auth import get_api_key
87
+
88
+ async def call_api():
89
+ api_key = get_api_key()
90
+ if not api_key:
91
+ return {"error": "No API key provided"}
92
+
93
+ # Use the API key in your request
94
+ headers = {"Authorization": f"Bearer {api_key}"}
95
+ ...
96
+ """
97
+ return _current_api_key.get()
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 advanced)"
72
+ "basic", "--template", "-t", help="Template to use (basic or api_key)"
73
73
  ),
74
74
  ) -> None:
75
75
  """Initialize a new GolfMCP project.
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 advanced)
27
+ template: Template to use (basic or api_key)
28
28
  """
29
29
  # Validate template
30
- if template not in ("basic", "advanced"):
30
+ valid_templates = ("basic", "api_key")
31
+ if template not in valid_templates:
31
32
  console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
32
- console.print("Available templates: basic, advanced")
33
+ console.print(f"Available templates: {', '.join(valid_templates)}")
33
34
  track_event("cli_init_failed", {"success": False})
34
35
  return
35
36
 
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,77 @@ 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
+ "from starlette.responses import JSONResponse",
140
+ "",
141
+ f"mcp = FastMCP({repr(server_name)})",
142
+ "",
143
+ "# Middleware to extract API key from headers",
144
+ "class ApiKeyMiddleware(BaseHTTPMiddleware):",
145
+ " async def dispatch(self, request: Request, call_next):",
146
+ " api_key_config = get_api_key_config()",
147
+ " if api_key_config:",
148
+ " # Extract API key from the configured header",
149
+ " header_name = api_key_config.header_name",
150
+ " header_prefix = api_key_config.header_prefix",
151
+ " is_required = api_key_config.required",
152
+ " ",
153
+ " # Case-insensitive header lookup",
154
+ " api_key = None",
155
+ " for k, v in request.headers.items():",
156
+ " if k.lower() == header_name.lower():",
157
+ " api_key = v",
158
+ " break",
159
+ " ",
160
+ " # Strip prefix if configured and present",
161
+ " if api_key and header_prefix and api_key.startswith(header_prefix):",
162
+ " api_key = api_key[len(header_prefix):]",
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
+ " ",
172
+ " # Store the API key in context for tools to access",
173
+ " set_api_key(api_key)",
174
+ " ",
175
+ " # Continue with the request",
176
+ " response = await call_next(request)",
177
+ " return response",
178
+ "",
179
+ "# Add the middleware to the FastMCP app",
180
+ "mcp.app.add_middleware(ApiKeyMiddleware)",
181
+ ])
182
+
183
+ return "\n".join(generated_code_lines)
184
+
185
+
117
186
  def generate_auth_routes() -> str:
118
187
  """Generate code for OAuth routes in the FastMCP app.
119
188
  These routes are added to the FastMCP instance (`mcp`) created by `generate_auth_code`.
120
189
  """
190
+ # API key auth doesn't need special routes
191
+ api_key_config = get_api_key_config()
192
+ if api_key_config:
193
+ return ""
194
+
121
195
  provider_config, _ = get_auth_config() # Used to check if auth is enabled generally
122
196
  if not provider_config:
123
197
  return ""
@@ -0,0 +1,2 @@
1
+ OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318/v1/traces"
2
+ OTEL_SERVICE_NAME="golf-mcp"
@@ -0,0 +1,84 @@
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
+ required=True # Reject requests without a valid API key
40
+ )
41
+ ```
42
+
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`
47
+
48
+ ## How It Works
49
+
50
+ 1. **Client sends request** with GitHub token in the Authorization header
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
57
+
58
+ ## Running the Server
59
+
60
+ 1. Build and run:
61
+ ```bash
62
+ golf build
63
+ golf run
64
+ ```
65
+
66
+ 2. The server will start on `http://127.0.0.1:3000` (configurable in `golf.json`)
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
+ ```
76
+
77
+ ## GitHub Token Permissions
78
+
79
+ Depending on which tools you use, you'll need different token permissions:
80
+
81
+ - **Public repositories**: No token needed for read-only access
82
+ - **Private repositories**: Token with `repo` scope
83
+ - **Creating issues**: Token with `repo` or `public_repo` scope
84
+ - **User information**: Token with `user` scope
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "github-mcp-server",
3
+ "description": "MCP server for GitHub API operations with API key authentication",
4
+ "host": "127.0.0.1",
5
+ "port": 3000,
6
+ "transport": "sse"
7
+ }
@@ -0,0 +1,11 @@
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
+ required=True # Reject requests without a valid API key
11
+ )
@@ -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/.env CHANGED
@@ -1,3 +1,5 @@
1
1
  GITHUB_CLIENT_ID="Ov23liPVrFkEzGhXro5A"
2
2
  GITHUB_CLIENT_SECRET="4f050336d569559705963d88cf8ec8b3ce10441a"
3
- JWT_SECRET="example-jwt-secret-for-development-only"
3
+ JWT_SECRET="example-jwt-secret-for-development-only"
4
+ OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318/v1/traces"
5
+ OTEL_SERVICE_NAME="golf-mcp"
@@ -3,7 +3,5 @@
3
3
  "description": "A GolfMCP project",
4
4
  "host": "127.0.0.1",
5
5
  "port": 3000,
6
- "transport": "sse",
7
- "opentelemetry_enabled": false,
8
- "opentelemetry_default_exporter": "console"
6
+ "transport": "sse"
9
7
  }
@@ -1,6 +1,7 @@
1
1
  """Hello World tool {{project_name}}."""
2
2
 
3
- from pydantic import BaseModel
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 pydantic import BaseModel
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
- card_token: str,
18
- description: str = ""
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 pydantic import BaseModel
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
- amount: float,
18
- reason: str = "customer_request"
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 of ${amount:.2f} for charge {charge_id}...")
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 ${amount:.2f}"
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.5
3
+ Version: 0.1.7
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -139,15 +139,16 @@ Creating a new tool is as simple as adding a Python file to the `tools/` directo
139
139
  # tools/hello.py
140
140
  """Hello World tool {{project_name}}."""
141
141
 
142
- from pydantic import BaseModel
142
+ from typing import Annotated
143
+ from pydantic import BaseModel, Field
143
144
 
144
145
  class Output(BaseModel):
145
146
  """Response from the hello tool."""
146
147
  message: str
147
148
 
148
149
  async def hello(
149
- name: str = "World",
150
- greeting: str = "Hello"
150
+ name: Annotated[str, Field(description="The name of the person to greet")] = "World",
151
+ greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
151
152
  ) -> Output:
152
153
  """Say hello to the given name.
153
154
 
@@ -188,9 +189,18 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
188
189
  - `"stdio"` enables integration with command-line tools and scripts
189
190
  - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
190
191
 
192
+ ## Roadmap
193
+
194
+ Here are the things we are working hard on:
195
+
196
+ * **Native OpenTelemetry implementation for tracing**
197
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
198
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
199
+
200
+
191
201
  ## Privacy & Telemetry
192
202
 
193
- Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
203
+ Golf collects **anonymous** usage data on the CLI to help us understand how the framework is being used and improve it over time. The data collected includes:
194
204
 
195
205
  - Commands run (init, build, run)
196
206
  - Success/failure status (no error details)
@@ -218,23 +228,8 @@ You can disable telemetry in several ways:
218
228
  golf init my-project --no-telemetry
219
229
  ```
220
230
 
221
- 3. **Environment variable** (temporary override):
222
- ```bash
223
- export GOLF_TELEMETRY=0
224
- golf init my-project
225
- ```
226
-
227
231
  Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
228
232
 
229
- ## Roadmap
230
-
231
- Here are the things we are working hard on:
232
-
233
- * **Native OpenTelemetry implementation for tracing**
234
- * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
235
- * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
236
-
237
-
238
233
  <div align="center">
239
234
  Made with ❤️ in Warsaw, Poland and SF
240
235
  </div>
@@ -0,0 +1,52 @@
1
+ golf/__init__.py,sha256=GmypIHlw9-BaSEaoucCIwm0ut1DUut0hUvsyTCr17qk,21
2
+ golf/auth/__init__.py,sha256=nh22WdganrEtp0YnxX1dmk6euuUFu8zh1BWxi1LGZnI,4107
3
+ golf/auth/api_key.py,sha256=TWJJ1tyVPSp1vN61opn3DdGKfQkTLX2w-YyLO4eeRPk,2495
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=c2Fpb9pZj-fmReVCYMfuPizMm1UC7gf-uoF0S65Zxu8,12370
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/.env,sha256=15dewTdeJEMAIuzQmh1SFc1zEN6PwryWgAc14IV02lY,90
23
+ golf/examples/api_key/README.md,sha256=nuc4YIspX_eokoArylBv6rKPv5P8BTq__2YaInidrUk,2892
24
+ golf/examples/api_key/golf.json,sha256=iBSQWsazl6k7xdg1GhSJkzx4dANDjUp0WGFrI8M9Bug,180
25
+ golf/examples/api_key/pre_build.py,sha256=JfevA986fodhgDv9JEXtgB9mn7SQEXbBaSoGGR9XyYY,469
26
+ golf/examples/api_key/tools/issues/create.py,sha256=b58IadolStcuRxpRdTC_78Kfa4KCvMJSMvAlCouz-Y0,2832
27
+ golf/examples/api_key/tools/issues/list.py,sha256=k2TGEgVCCTs0flHQFRqKQIA2zL39JTlTiUgAymW2gVg,2777
28
+ golf/examples/api_key/tools/repos/list.py,sha256=4Z9ISphjhSZdJ3xqxmwbSo7UvkT368ZiSdyckOZqZnQ,3272
29
+ golf/examples/api_key/tools/search/code.py,sha256=eXpJAmKq0am4oiCZm8vfz9d5NmyxjpnMPcEwrCf8vNo,3025
30
+ golf/examples/api_key/tools/users/get.py,sha256=ifzqLxUr0dX00xscyjwuXCB3n48znu28DQU8MGUor50,2577
31
+ golf/examples/basic/.env,sha256=CqdcvPXopWppurJ3bBjT2dODlKUrLv629BHnOy8zBkM,247
32
+ golf/examples/basic/.env.example,sha256=gCTFcabTvr_-Rg3kTGmi63JQtCEOwMu4U5jTa1GM66U,123
33
+ golf/examples/basic/README.md,sha256=-mY3R6AAnkXT9FPkALDrJtdf9IyKDvuqjsrLAMTLRYI,2663
34
+ golf/examples/basic/golf.json,sha256=IcorojR67GaOZRrOj_XB6gyRXha7Jrwyc8IHtJM13DU,132
35
+ golf/examples/basic/pre_build.py,sha256=VVYtBSCy4Xjg1Ocpl8ZwEr7VCVqPSSkDEzrTfQkAQKY,1037
36
+ golf/examples/basic/prompts/welcome.py,sha256=LgT1eQewe0J08I5WFBv4RrgDr6yn4a4ww6XQ8k3sTVk,822
37
+ golf/examples/basic/resources/current_time.py,sha256=KG28G5tpFGtGH2T5DHFAc-CwbYsFzE115xmGMVpi7fs,1176
38
+ golf/examples/basic/resources/info.py,sha256=w4eLMapn9M4TN2dvXga5fudAnesFIyst-WgImxeApRQ,735
39
+ golf/examples/basic/resources/weather/common.py,sha256=fmbeUm8DNSg4X48G9krPuv9WK7q42LRvbR4FTn2H3PU,1749
40
+ golf/examples/basic/resources/weather/current.py,sha256=YC3QW5PQxFEWlEQid3pkowrhyqTF0coQDBj7V-M-WeQ,943
41
+ golf/examples/basic/resources/weather/forecast.py,sha256=FpYmgUHjj8bDeWdaX0Lph_XE0QnXOOcI6nwMwB3zhBo,1000
42
+ golf/examples/basic/tools/github_user.py,sha256=hLJ9C4ase7i8nUetLWvEVni8EOvsXZS0Ilhr-Ec0lcw,1975
43
+ golf/examples/basic/tools/hello.py,sha256=wJjCYtWfRpTAcNKrlrRibm0UFKLnHFB1sGjR-q90GGk,867
44
+ golf/examples/basic/tools/payments/charge.py,sha256=BxgHtVfZA3Z_QhT3OJisKyfGr3ZWBzd-jlgdwkvZaYM,1736
45
+ golf/examples/basic/tools/payments/common.py,sha256=z5shSOZizUEnxBmGUSYghxmNwIvuhg-yfXKIfPWgV04,1292
46
+ golf/examples/basic/tools/payments/refund.py,sha256=3auhKzuwPLgpoyUDz0p8lUWUY_fXNe0RYadQBxEvpB4,1603
47
+ golf_mcp-0.1.7.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
48
+ golf_mcp-0.1.7.dist-info/METADATA,sha256=RoY48fz5Fqyg2lzsq-HFUX7xK6L6bqMjoOpuq286bkg,9442
49
+ golf_mcp-0.1.7.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
50
+ golf_mcp-0.1.7.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
51
+ golf_mcp-0.1.7.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
52
+ golf_mcp-0.1.7.dist-info/RECORD,,
@@ -1,42 +0,0 @@
1
- golf/__init__.py,sha256=Nmswip0IUvJenHIhdfSyTYurDcwWTvOQ8mPDREtwE1o,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=mjkr6VaHABiu1SLyaFG6T8EfdvWjyNZVWY6FEWp-WHU,7385
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.5.dist-info/licenses/LICENSE,sha256=5_j2f6fTJmvfmUewzElhkpAaXg2grVoxKouOA8ihV6E,11348
38
- golf_mcp-0.1.5.dist-info/METADATA,sha256=GY8Uk1nt-hA4FPx8ds_ZwxGTb-DQ5wlIwIDRMNhv82I,9390
39
- golf_mcp-0.1.5.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
40
- golf_mcp-0.1.5.dist-info/entry_points.txt,sha256=5y7rHYM8jGpU-nfwdknCm5XsApLulqsnA37MO6BUTYg,43
41
- golf_mcp-0.1.5.dist-info/top_level.txt,sha256=BQToHcBUufdyhp9ONGMIvPE40jMEtmI20lYaKb4hxOg,5
42
- golf_mcp-0.1.5.dist-info/RECORD,,