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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.4"
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
- _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,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" # Replace with your actual PostHog project API key
20
+ DEFAULT_POSTHOG_API_KEY = "phc_7ccsDDxoC5tK5hodlrs2moGC74cThRzcN63flRYPWGl"
21
21
  POSTHOG_API_KEY = os.environ.get("GOLF_POSTHOG_API_KEY", DEFAULT_POSTHOG_API_KEY)
22
22
  POSTHOG_HOST = "https://us.i.posthog.com"
23
23
 
@@ -156,8 +156,8 @@ def initialize_telemetry() -> None:
156
156
  if not is_telemetry_enabled():
157
157
  return
158
158
 
159
- # Skip initialization if no valid API key
160
- if not POSTHOG_API_KEY or POSTHOG_API_KEY == DEFAULT_POSTHOG_API_KEY:
159
+ # Skip initialization if no valid API key (empty or placeholder)
160
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY.startswith("phc_YOUR"):
161
161
  return
162
162
 
163
163
  try:
@@ -183,8 +183,8 @@ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) ->
183
183
  if not is_telemetry_enabled():
184
184
  return
185
185
 
186
- # Skip if no valid API key
187
- if not POSTHOG_API_KEY or POSTHOG_API_KEY == DEFAULT_POSTHOG_API_KEY:
186
+ # Skip if no valid API key (empty or placeholder)
187
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY.startswith("phc_YOUR"):
188
188
  return
189
189
 
190
190
  try:
@@ -0,0 +1,70 @@
1
+ # GitHub MCP Server with API Key Authentication
2
+
3
+ This example demonstrates how to build a GitHub API MCP server using Golf's API key authentication feature. The server wraps common GitHub operations and passes through authentication tokens from MCP clients to the GitHub API.
4
+
5
+ ## Features
6
+
7
+ This MCP server provides tools for:
8
+
9
+ - **Repository Management**
10
+ - `list-repos` - List repositories for users, organizations, or the authenticated user
11
+
12
+ - **Issue Management**
13
+ - `create-issues` - Create new issues with labels
14
+ - `list-issues` - List and filter issues by state and labels
15
+
16
+ - **Code Search**
17
+ - `code-search` - Search for code across GitHub with language and repository filters
18
+
19
+ - **User Information**
20
+ - `get-users` - Get user profiles or verify authentication
21
+
22
+ ## Tool Naming Convention
23
+
24
+ Golf automatically derives tool names from the file structure:
25
+ - `tools/issues/create.py` → `create-issues`
26
+ - `tools/issues/list.py` → `list-issues`
27
+ - `tools/repos/list.py` → `list-repos`
28
+ - `tools/search/code.py` → `code-search`
29
+ - `tools/users/get.py` → `get-users`
30
+
31
+ ## Configuration
32
+
33
+ The server is configured in `pre_build.py` to extract GitHub tokens from the `Authorization` header:
34
+
35
+ ```python
36
+ configure_api_key(
37
+ header_name="Authorization",
38
+ header_prefix="Bearer "
39
+ )
40
+ ```
41
+
42
+ This configuration handles GitHub's token format: `Authorization: Bearer ghp_xxxxxxxxxxxx`
43
+
44
+ ## How It Works
45
+
46
+ 1. **Client sends request** with GitHub token in the Authorization header
47
+ 2. **Golf middleware** extracts the token based on your configuration
48
+ 3. **Tools retrieve token** using `get_api_key()`
49
+ 4. **Token is forwarded** to GitHub API in the appropriate format
50
+ 5. **GitHub validates** the token and returns results
51
+
52
+ ## Running the Server
53
+
54
+ 1. Build and run:
55
+ ```bash
56
+ golf build dev
57
+ golf run
58
+ ```
59
+
60
+ 2. The server will start on `http://127.0.0.1:3000` (configurable in `golf.json`)
61
+
62
+
63
+ ## GitHub Token Permissions
64
+
65
+ Depending on which tools you use, you'll need different token permissions:
66
+
67
+ - **Public repositories**: No token needed for read-only access
68
+ - **Private repositories**: Token with `repo` scope
69
+ - **Creating issues**: Token with `repo` or `public_repo` scope
70
+ - **User information**: Token with `user` scope
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "github-mcp-server",
3
+ "description": "MCP server for GitHub API operations with API key authentication",
4
+ "host": "127.0.0.1",
5
+ "port": 3000,
6
+ "transport": "sse"
7
+ }
@@ -0,0 +1,10 @@
1
+ """Configure API key authentication for GitHub MCP server."""
2
+
3
+ from golf.auth import configure_api_key
4
+
5
+ # Configure Golf to extract GitHub personal access tokens from the Authorization header
6
+ # GitHub expects: Authorization: Bearer ghp_xxxx or Authorization: token ghp_xxxx
7
+ configure_api_key(
8
+ header_name="Authorization",
9
+ header_prefix="Bearer " # Will handle both "Bearer " and "token " prefixes
10
+ )
@@ -0,0 +1,94 @@
1
+ """Create a new issue in a GitHub repository."""
2
+
3
+ from typing import Annotated, List, Optional
4
+ from pydantic import BaseModel, Field
5
+ import httpx
6
+
7
+ from golf.auth import get_api_key
8
+
9
+
10
+ class Output(BaseModel):
11
+ """Response from creating an issue."""
12
+ success: bool
13
+ issue_number: Optional[int] = None
14
+ issue_url: Optional[str] = None
15
+ error: Optional[str] = None
16
+
17
+
18
+ async def create(
19
+ repo: Annotated[str, Field(description="Repository name in format 'owner/repo'")],
20
+ title: Annotated[str, Field(description="Issue title")],
21
+ body: Annotated[str, Field(description="Issue description/body (supports Markdown)")] = "",
22
+ labels: Annotated[Optional[List[str]], Field(description="List of label names to apply")] = None
23
+ ) -> Output:
24
+ """Create a new issue.
25
+
26
+ Requires authentication with appropriate permissions.
27
+ """
28
+ github_token = get_api_key()
29
+
30
+ if not github_token:
31
+ return Output(
32
+ success=False,
33
+ error="Authentication required. Please provide a GitHub token."
34
+ )
35
+
36
+ # Validate repo format
37
+ if '/' not in repo:
38
+ return Output(
39
+ success=False,
40
+ error="Repository must be in format 'owner/repo'"
41
+ )
42
+
43
+ url = f"https://api.github.com/repos/{repo}/issues"
44
+
45
+ # Build request payload
46
+ payload = {
47
+ "title": title,
48
+ "body": body
49
+ }
50
+ if labels:
51
+ payload["labels"] = labels
52
+
53
+ try:
54
+ async with httpx.AsyncClient() as client:
55
+ response = await client.post(
56
+ url,
57
+ headers={
58
+ "Authorization": f"Bearer {github_token}",
59
+ "Accept": "application/vnd.github.v3+json",
60
+ "User-Agent": "Golf-GitHub-MCP-Server"
61
+ },
62
+ json=payload,
63
+ timeout=10.0
64
+ )
65
+
66
+ response.raise_for_status()
67
+ issue_data = response.json()
68
+
69
+ return Output(
70
+ success=True,
71
+ issue_number=issue_data["number"],
72
+ issue_url=issue_data["html_url"]
73
+ )
74
+
75
+ except httpx.HTTPStatusError as e:
76
+ error_messages = {
77
+ 401: "Invalid or missing authentication token",
78
+ 403: "Insufficient permissions to create issues in this repository",
79
+ 404: "Repository not found",
80
+ 422: "Invalid request data"
81
+ }
82
+ return Output(
83
+ success=False,
84
+ error=error_messages.get(e.response.status_code, f"GitHub API error: {e.response.status_code}")
85
+ )
86
+ except Exception as e:
87
+ return Output(
88
+ success=False,
89
+ error=f"Failed to create issue: {str(e)}"
90
+ )
91
+
92
+
93
+ # Export the function to be used as the tool
94
+ export = create
@@ -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
@@ -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.4
3
+ Version: 0.1.6
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -139,15 +139,16 @@ Creating a new tool is as simple as adding a Python file to the `tools/` directo
139
139
  # tools/hello.py
140
140
  """Hello World tool {{project_name}}."""
141
141
 
142
- from pydantic import BaseModel
142
+ from typing import Annotated
143
+ from pydantic import BaseModel, Field
143
144
 
144
145
  class Output(BaseModel):
145
146
  """Response from the hello tool."""
146
147
  message: str
147
148
 
148
149
  async def hello(
149
- name: str = "World",
150
- greeting: str = "Hello"
150
+ name: Annotated[str, Field(description="The name of the person to greet")] = "World",
151
+ greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
151
152
  ) -> Output:
152
153
  """Say hello to the given name.
153
154
 
@@ -188,9 +189,18 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
188
189
  - `"stdio"` enables integration with command-line tools and scripts
189
190
  - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
190
191
 
192
+ ## Roadmap
193
+
194
+ Here are the things we are working hard on:
195
+
196
+ * **Native OpenTelemetry implementation for tracing**
197
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
198
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
199
+
200
+
191
201
  ## Privacy & Telemetry
192
202
 
193
- Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
203
+ Golf collects **anonymous** usage data on the CLI to help us understand how the framework is being used and improve it over time. The data collected includes:
194
204
 
195
205
  - Commands run (init, build, run)
196
206
  - Success/failure status (no error details)
@@ -218,23 +228,8 @@ You can disable telemetry in several ways:
218
228
  golf init my-project --no-telemetry
219
229
  ```
220
230
 
221
- 3. **Environment variable** (temporary override):
222
- ```bash
223
- export GOLF_TELEMETRY=0
224
- golf init my-project
225
- ```
226
-
227
231
  Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
228
232
 
229
- ## Roadmap
230
-
231
- Here are the things we are working hard on:
232
-
233
- * **Native OpenTelemetry implementation for tracing**
234
- * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
235
- * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
236
-
237
-
238
233
  <div align="center">
239
234
  Made with ❤️ in Warsaw, Poland and SF
240
235
  </div>
@@ -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,,
@@ -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,,