golf-mcp 0.1.20__py3-none-any.whl → 0.2.0__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 +9 -1
- golf/_endpoints.py +6 -0
- golf/_endpoints_fallback.py +10 -0
- golf/auth/__init__.py +188 -84
- golf/auth/api_key.py +6 -14
- golf/auth/factory.py +333 -0
- golf/auth/helpers.py +12 -42
- golf/auth/providers.py +396 -0
- golf/auth/registry.py +256 -0
- golf/cli/branding.py +192 -0
- golf/cli/main.py +28 -69
- golf/commands/__init__.py +2 -0
- golf/commands/build.py +4 -7
- golf/commands/init.py +30 -53
- golf/commands/run.py +50 -20
- golf/core/builder.py +355 -414
- golf/core/builder_auth.py +63 -144
- golf/core/builder_telemetry.py +26 -3
- golf/core/config.py +38 -59
- golf/core/parser.py +132 -139
- golf/core/platform.py +12 -10
- golf/core/telemetry.py +11 -19
- golf/core/transformer.py +38 -15
- golf/examples/__pycache__/__init__.cpython-311.pyc +0 -0
- golf/examples/basic/.coverage +0 -0
- golf/examples/basic/.env.example +8 -4
- golf/examples/basic/README.md +117 -45
- golf/examples/basic/__pycache__/auth.cpython-311.pyc +0 -0
- golf/examples/basic/auth.py +76 -0
- golf/examples/basic/golf.json +2 -5
- golf/examples/basic/htmlcov/.gitignore +2 -0
- golf/examples/basic/htmlcov/class_index.html +547 -0
- golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +733 -0
- golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
- golf/examples/basic/htmlcov/function_index.html +2091 -0
- golf/examples/basic/htmlcov/index.html +349 -0
- golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
- golf/examples/basic/htmlcov/status.json +1 -0
- golf/examples/basic/htmlcov/style_cb_8e611ae1.css +337 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +323 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +170 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +430 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +288 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +493 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +353 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +120 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +1535 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +289 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +476 -0
- golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +97 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +102 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +178 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +387 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +222 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +106 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +107 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +306 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +329 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +1471 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +186 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +315 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +1149 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +279 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +589 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +286 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +107 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +417 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +150 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +267 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +318 -0
- golf/examples/basic/prompts/__pycache__/welcome.cpython-311.pyc +0 -0
- golf/examples/basic/prompts/welcome.py +3 -5
- golf/examples/basic/resources/__pycache__/current_time.cpython-311.pyc +0 -0
- golf/examples/basic/resources/__pycache__/info.cpython-311.pyc +0 -0
- golf/examples/basic/resources/current_time.py +5 -13
- golf/examples/basic/resources/weather/__pycache__/common.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/current.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/forecast.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/city.py +46 -0
- golf/examples/basic/resources/weather/common.py +4 -11
- golf/examples/basic/resources/weather/current.py +5 -5
- golf/examples/basic/resources/weather/forecast.py +5 -5
- golf/examples/basic/tools/__pycache__/calculator.cpython-311.pyc +0 -0
- golf/examples/basic/tools/calculator.py +94 -0
- golf/examples/basic/tools/say/__pycache__/hello.cpython-311.pyc +0 -0
- golf/examples/basic/tools/say/hello.py +65 -0
- golf/metrics/collector.py +100 -19
- golf/telemetry/__init__.py +4 -0
- golf/telemetry/instrumentation.py +484 -178
- golf/utilities/__init__.py +12 -0
- golf/utilities/context.py +53 -0
- golf/utilities/elicitation.py +170 -0
- golf/utilities/sampling.py +221 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/METADATA +51 -104
- golf_mcp-0.2.0.dist-info/RECORD +110 -0
- golf/auth/oauth.py +0 -861
- golf/auth/provider.py +0 -115
- golf/examples/api_key/.env +0 -2
- golf/examples/api_key/.env.example +0 -1
- golf/examples/api_key/README.md +0 -84
- golf/examples/api_key/golf.json +0 -8
- golf/examples/api_key/pre_build.py +0 -11
- golf/examples/api_key/tools/issues/create.py +0 -93
- golf/examples/api_key/tools/issues/list.py +0 -92
- golf/examples/api_key/tools/repos/list.py +0 -111
- golf/examples/api_key/tools/search/code.py +0 -106
- golf/examples/api_key/tools/users/get.py +0 -82
- golf/examples/basic/.env +0 -5
- golf/examples/basic/pre_build.py +0 -28
- golf/examples/basic/tools/github_user.py +0 -65
- golf/examples/basic/tools/hello.py +0 -34
- golf/examples/basic/tools/payments/charge.py +0 -70
- golf/examples/basic/tools/payments/common.py +0 -36
- golf/examples/basic/tools/payments/refund.py +0 -61
- golf_mcp-0.1.20.dist-info/RECORD +0 -60
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/top_level.txt +0 -0
golf/auth/provider.py
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
"""OAuth provider configuration for GolfMCP authentication.
|
|
2
|
-
|
|
3
|
-
This module defines the ProviderConfig class used to configure
|
|
4
|
-
OAuth authentication for GolfMCP servers.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from pydantic import BaseModel, Field, field_validator
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ProviderConfig(BaseModel):
|
|
13
|
-
"""Configuration for an OAuth2 provider.
|
|
14
|
-
|
|
15
|
-
This class defines the configuration for an OAuth2 provider,
|
|
16
|
-
including the endpoints, credentials, and other settings needed
|
|
17
|
-
to authenticate with the provider.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
# Provider identification
|
|
21
|
-
provider: str = Field(
|
|
22
|
-
..., description="Provider type (e.g., 'github', 'google', 'custom')"
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# OAuth credentials - names of environment variables to read at runtime
|
|
26
|
-
client_id_env_var: str = Field(
|
|
27
|
-
..., description="Name of environment variable for Client ID"
|
|
28
|
-
)
|
|
29
|
-
client_secret_env_var: str = Field(
|
|
30
|
-
..., description="Name of environment variable for Client Secret"
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
# These fields will store the actual values read at runtime in dist/server.py
|
|
34
|
-
# They are made optional here as they are resolved in the generated code.
|
|
35
|
-
client_id: str | None = Field(
|
|
36
|
-
None, description="OAuth client ID (resolved at runtime)"
|
|
37
|
-
)
|
|
38
|
-
client_secret: str | None = Field(
|
|
39
|
-
None, description="OAuth client secret (resolved at runtime)"
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# OAuth endpoints (can be baked in)
|
|
43
|
-
authorize_url: str = Field(..., description="Authorization endpoint URL")
|
|
44
|
-
token_url: str = Field(..., description="Token endpoint URL")
|
|
45
|
-
userinfo_url: str | None = Field(
|
|
46
|
-
None, description="User info endpoint URL (for OIDC providers)"
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
jwks_uri: str | None = Field(
|
|
50
|
-
None, description="JSON Web Key Set URI (for token validation)"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
scopes: list[str] = Field(
|
|
54
|
-
default_factory=list, description="OAuth scopes to request from the provider"
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
issuer_url: str | None = Field(
|
|
58
|
-
None,
|
|
59
|
-
description="OIDC issuer URL for discovery (if using OIDC) - will be overridden by runtime value in server.py",
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
callback_path: str = Field(
|
|
63
|
-
"/auth/callback",
|
|
64
|
-
description="Path on this server where the IdP should redirect after authentication",
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# JWT configuration
|
|
68
|
-
jwt_secret_env_var: str = Field(
|
|
69
|
-
..., description="Name of environment variable for JWT Secret"
|
|
70
|
-
)
|
|
71
|
-
jwt_secret: str | None = Field(
|
|
72
|
-
None, description="Secret key for signing JWT tokens (resolved at runtime)"
|
|
73
|
-
)
|
|
74
|
-
token_expiration: int = Field(
|
|
75
|
-
3600, description="JWT token expiration time in seconds", ge=60, le=86400
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
settings: dict[str, Any] = Field(
|
|
79
|
-
default_factory=dict, description="Additional provider-specific settings"
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
@field_validator("provider")
|
|
83
|
-
@classmethod
|
|
84
|
-
def validate_provider(cls, value: str) -> str:
|
|
85
|
-
"""Validate the provider type.
|
|
86
|
-
|
|
87
|
-
Ensures the provider type is a valid, supported provider.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
value: The provider type
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
The validated provider type
|
|
94
|
-
|
|
95
|
-
Raises:
|
|
96
|
-
ValueError: If the provider type is not supported
|
|
97
|
-
"""
|
|
98
|
-
known_providers = {"custom", "github", "google", "jwks"}
|
|
99
|
-
|
|
100
|
-
if value not in known_providers and not value.startswith("custom:"):
|
|
101
|
-
raise ValueError(
|
|
102
|
-
f"Unknown provider: '{value}'. Must be one of {known_providers} "
|
|
103
|
-
"or start with 'custom:'"
|
|
104
|
-
)
|
|
105
|
-
return value
|
|
106
|
-
|
|
107
|
-
def get_provider_name(self) -> str:
|
|
108
|
-
"""Get a clean provider name for display purposes.
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
A human-readable provider name
|
|
112
|
-
"""
|
|
113
|
-
if self.provider.startswith("custom:"):
|
|
114
|
-
return self.provider[7:] # Remove 'custom:' prefix
|
|
115
|
-
return self.provider.capitalize()
|
golf/examples/api_key/.env
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#GOLF_API_KEY=<your-api-key>
|
golf/examples/api_key/README.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
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
|
golf/examples/api_key/golf.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
)
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"""Create a new issue in a GitHub repository."""
|
|
2
|
-
|
|
3
|
-
from typing import Annotated
|
|
4
|
-
|
|
5
|
-
import httpx
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
7
|
-
|
|
8
|
-
from golf.auth import get_api_key
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Output(BaseModel):
|
|
12
|
-
"""Response from creating an issue."""
|
|
13
|
-
|
|
14
|
-
success: bool
|
|
15
|
-
issue_number: int | None = None
|
|
16
|
-
issue_url: str | None = None
|
|
17
|
-
error: str | None = None
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
async def create(
|
|
21
|
-
repo: Annotated[str, Field(description="Repository name in format 'owner/repo'")],
|
|
22
|
-
title: Annotated[str, Field(description="Issue title")],
|
|
23
|
-
body: Annotated[
|
|
24
|
-
str, Field(description="Issue description/body (supports Markdown)")
|
|
25
|
-
] = "",
|
|
26
|
-
labels: Annotated[
|
|
27
|
-
list[str] | None, Field(description="List of label names to apply")
|
|
28
|
-
] = None,
|
|
29
|
-
) -> Output:
|
|
30
|
-
"""Create a new issue.
|
|
31
|
-
|
|
32
|
-
Requires authentication with appropriate permissions.
|
|
33
|
-
"""
|
|
34
|
-
github_token = get_api_key()
|
|
35
|
-
|
|
36
|
-
if not github_token:
|
|
37
|
-
return Output(
|
|
38
|
-
success=False,
|
|
39
|
-
error="Authentication required. Please provide a GitHub token.",
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# Validate repo format
|
|
43
|
-
if "/" not in repo:
|
|
44
|
-
return Output(success=False, error="Repository must be in format 'owner/repo'")
|
|
45
|
-
|
|
46
|
-
url = f"https://api.github.com/repos/{repo}/issues"
|
|
47
|
-
|
|
48
|
-
# Build request payload
|
|
49
|
-
payload = {"title": title, "body": body}
|
|
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(
|
|
85
|
-
e.response.status_code, f"GitHub API error: {e.response.status_code}"
|
|
86
|
-
),
|
|
87
|
-
)
|
|
88
|
-
except Exception as e:
|
|
89
|
-
return Output(success=False, error=f"Failed to create issue: {str(e)}")
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Export the function to be used as the tool
|
|
93
|
-
export = create
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"""List issues in a GitHub repository."""
|
|
2
|
-
|
|
3
|
-
from typing import Annotated, Any
|
|
4
|
-
|
|
5
|
-
import httpx
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
7
|
-
|
|
8
|
-
from golf.auth import get_api_key
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Output(BaseModel):
|
|
12
|
-
"""List of issues from the repository."""
|
|
13
|
-
|
|
14
|
-
issues: list[dict[str, Any]]
|
|
15
|
-
total_count: int
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def list(
|
|
19
|
-
repo: Annotated[str, Field(description="Repository name in format 'owner/repo'")],
|
|
20
|
-
state: Annotated[
|
|
21
|
-
str, Field(description="Filter by state - 'open', 'closed', or 'all'")
|
|
22
|
-
] = "open",
|
|
23
|
-
labels: Annotated[
|
|
24
|
-
str | None,
|
|
25
|
-
Field(description="Comma-separated list of label names to filter by"),
|
|
26
|
-
] = None,
|
|
27
|
-
per_page: Annotated[
|
|
28
|
-
int, Field(description="Number of results per page (max 100)")
|
|
29
|
-
] = 20,
|
|
30
|
-
) -> Output:
|
|
31
|
-
"""List issues in a repository.
|
|
32
|
-
|
|
33
|
-
Returns issues with their number, title, state, and other metadata.
|
|
34
|
-
Pull requests are filtered out from the results.
|
|
35
|
-
"""
|
|
36
|
-
github_token = get_api_key()
|
|
37
|
-
|
|
38
|
-
# Validate repo format
|
|
39
|
-
if "/" not in repo:
|
|
40
|
-
return Output(issues=[], total_count=0)
|
|
41
|
-
|
|
42
|
-
url = f"https://api.github.com/repos/{repo}/issues"
|
|
43
|
-
|
|
44
|
-
# Prepare headers
|
|
45
|
-
headers = {
|
|
46
|
-
"Accept": "application/vnd.github.v3+json",
|
|
47
|
-
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
48
|
-
}
|
|
49
|
-
if github_token:
|
|
50
|
-
headers["Authorization"] = f"Bearer {github_token}"
|
|
51
|
-
|
|
52
|
-
# Build query parameters
|
|
53
|
-
params = {"state": state, "per_page": min(per_page, 100)}
|
|
54
|
-
if labels:
|
|
55
|
-
params["labels"] = labels
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
async with httpx.AsyncClient() as client:
|
|
59
|
-
response = await client.get(
|
|
60
|
-
url, headers=headers, params=params, timeout=10.0
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
response.raise_for_status()
|
|
64
|
-
issues_data = response.json()
|
|
65
|
-
|
|
66
|
-
# Filter out pull requests and format issues
|
|
67
|
-
issues = []
|
|
68
|
-
for issue in issues_data:
|
|
69
|
-
# Skip pull requests (they appear in issues endpoint too)
|
|
70
|
-
if "pull_request" in issue:
|
|
71
|
-
continue
|
|
72
|
-
|
|
73
|
-
issues.append(
|
|
74
|
-
{
|
|
75
|
-
"number": issue["number"],
|
|
76
|
-
"title": issue["title"],
|
|
77
|
-
"body": issue.get("body", ""),
|
|
78
|
-
"state": issue["state"],
|
|
79
|
-
"url": issue["html_url"],
|
|
80
|
-
"user": issue["user"]["login"],
|
|
81
|
-
"labels": [label["name"] for label in issue.get("labels", [])],
|
|
82
|
-
}
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
return Output(issues=issues, total_count=len(issues))
|
|
86
|
-
|
|
87
|
-
except Exception:
|
|
88
|
-
return Output(issues=[], total_count=0)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Export the function to be used as the tool
|
|
92
|
-
export = list
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
"""List GitHub repositories for a user or organization."""
|
|
2
|
-
|
|
3
|
-
from typing import Annotated, Any
|
|
4
|
-
|
|
5
|
-
import httpx
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
7
|
-
|
|
8
|
-
from golf.auth import get_api_key
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Output(BaseModel):
|
|
12
|
-
"""List of repositories."""
|
|
13
|
-
|
|
14
|
-
repositories: list[dict[str, Any]]
|
|
15
|
-
total_count: int
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def list(
|
|
19
|
-
username: Annotated[
|
|
20
|
-
str | None,
|
|
21
|
-
Field(
|
|
22
|
-
description="GitHub username (lists public repos, or all repos if authenticated as this user)"
|
|
23
|
-
),
|
|
24
|
-
] = None,
|
|
25
|
-
org: Annotated[
|
|
26
|
-
str | None,
|
|
27
|
-
Field(
|
|
28
|
-
description="GitHub organization name (lists public repos, or all repos if authenticated member)"
|
|
29
|
-
),
|
|
30
|
-
] = None,
|
|
31
|
-
sort: Annotated[
|
|
32
|
-
str,
|
|
33
|
-
Field(
|
|
34
|
-
description="How to sort results - 'created', 'updated', 'pushed', 'full_name'"
|
|
35
|
-
),
|
|
36
|
-
] = "updated",
|
|
37
|
-
per_page: Annotated[
|
|
38
|
-
int, Field(description="Number of results per page (max 100)")
|
|
39
|
-
] = 20,
|
|
40
|
-
) -> Output:
|
|
41
|
-
"""List GitHub repositories.
|
|
42
|
-
|
|
43
|
-
If neither username nor org is provided, lists repositories for the authenticated user.
|
|
44
|
-
"""
|
|
45
|
-
# Get the GitHub token from the request context
|
|
46
|
-
github_token = get_api_key()
|
|
47
|
-
|
|
48
|
-
# Determine the API endpoint
|
|
49
|
-
if org:
|
|
50
|
-
url = f"https://api.github.com/orgs/{org}/repos"
|
|
51
|
-
elif username:
|
|
52
|
-
url = f"https://api.github.com/users/{username}/repos"
|
|
53
|
-
else:
|
|
54
|
-
# List repos for authenticated user
|
|
55
|
-
if not github_token:
|
|
56
|
-
return Output(repositories=[], total_count=0)
|
|
57
|
-
url = "https://api.github.com/user/repos"
|
|
58
|
-
|
|
59
|
-
# Prepare headers
|
|
60
|
-
headers = {
|
|
61
|
-
"Accept": "application/vnd.github.v3+json",
|
|
62
|
-
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
63
|
-
}
|
|
64
|
-
if github_token:
|
|
65
|
-
headers["Authorization"] = f"Bearer {github_token}"
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
async with httpx.AsyncClient() as client:
|
|
69
|
-
response = await client.get(
|
|
70
|
-
url,
|
|
71
|
-
headers=headers,
|
|
72
|
-
params={
|
|
73
|
-
"sort": sort,
|
|
74
|
-
"per_page": min(per_page, 100),
|
|
75
|
-
"type": "all" if not username and not org else None,
|
|
76
|
-
},
|
|
77
|
-
timeout=10.0,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
response.raise_for_status()
|
|
81
|
-
repos_data = response.json()
|
|
82
|
-
|
|
83
|
-
# Format repositories
|
|
84
|
-
repositories = []
|
|
85
|
-
for repo in repos_data:
|
|
86
|
-
repositories.append(
|
|
87
|
-
{
|
|
88
|
-
"name": repo["name"],
|
|
89
|
-
"full_name": repo["full_name"],
|
|
90
|
-
"description": repo.get("description", ""),
|
|
91
|
-
"private": repo.get("private", False),
|
|
92
|
-
"stars": repo.get("stargazers_count", 0),
|
|
93
|
-
"forks": repo.get("forks_count", 0),
|
|
94
|
-
"language": repo.get("language", ""),
|
|
95
|
-
"url": repo["html_url"],
|
|
96
|
-
}
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
return Output(repositories=repositories, total_count=len(repositories))
|
|
100
|
-
|
|
101
|
-
except httpx.HTTPStatusError as e:
|
|
102
|
-
if e.response.status_code in [401, 404]:
|
|
103
|
-
return Output(repositories=[], total_count=0)
|
|
104
|
-
else:
|
|
105
|
-
return Output(repositories=[], total_count=0)
|
|
106
|
-
except Exception:
|
|
107
|
-
return Output(repositories=[], total_count=0)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
# Export the function to be used as the tool
|
|
111
|
-
export = list
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"""Search GitHub code."""
|
|
2
|
-
|
|
3
|
-
from typing import Annotated, Any
|
|
4
|
-
|
|
5
|
-
import httpx
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
7
|
-
|
|
8
|
-
from golf.auth import get_api_key
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Output(BaseModel):
|
|
12
|
-
"""Code search results."""
|
|
13
|
-
|
|
14
|
-
results: list[dict[str, Any]]
|
|
15
|
-
total_count: int
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def search(
|
|
19
|
-
query: Annotated[
|
|
20
|
-
str, Field(description="Search query (e.g., 'addClass', 'TODO', etc.)")
|
|
21
|
-
],
|
|
22
|
-
language: Annotated[
|
|
23
|
-
str | None,
|
|
24
|
-
Field(
|
|
25
|
-
description="Filter by programming language (e.g., 'python', 'javascript')"
|
|
26
|
-
),
|
|
27
|
-
] = None,
|
|
28
|
-
repo: Annotated[
|
|
29
|
-
str | None,
|
|
30
|
-
Field(description="Search within a specific repository (format: 'owner/repo')"),
|
|
31
|
-
] = None,
|
|
32
|
-
org: Annotated[
|
|
33
|
-
str | None,
|
|
34
|
-
Field(description="Search within repositories of a specific organization"),
|
|
35
|
-
] = None,
|
|
36
|
-
per_page: Annotated[
|
|
37
|
-
int, Field(description="Number of results per page (max 100)")
|
|
38
|
-
] = 10,
|
|
39
|
-
) -> Output:
|
|
40
|
-
"""Search for code on GitHub.
|
|
41
|
-
|
|
42
|
-
Without authentication, you're limited to 10 requests per minute.
|
|
43
|
-
With authentication, you can make up to 30 requests per minute.
|
|
44
|
-
"""
|
|
45
|
-
github_token = get_api_key()
|
|
46
|
-
|
|
47
|
-
# Build the search query
|
|
48
|
-
search_parts = [query]
|
|
49
|
-
if language:
|
|
50
|
-
search_parts.append(f"language:{language}")
|
|
51
|
-
if repo:
|
|
52
|
-
search_parts.append(f"repo:{repo}")
|
|
53
|
-
if org:
|
|
54
|
-
search_parts.append(f"org:{org}")
|
|
55
|
-
|
|
56
|
-
search_query = " ".join(search_parts)
|
|
57
|
-
|
|
58
|
-
url = "https://api.github.com/search/code"
|
|
59
|
-
|
|
60
|
-
# Prepare headers
|
|
61
|
-
headers = {
|
|
62
|
-
"Accept": "application/vnd.github.v3+json",
|
|
63
|
-
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
64
|
-
}
|
|
65
|
-
if github_token:
|
|
66
|
-
headers["Authorization"] = f"Bearer {github_token}"
|
|
67
|
-
|
|
68
|
-
try:
|
|
69
|
-
async with httpx.AsyncClient() as client:
|
|
70
|
-
response = await client.get(
|
|
71
|
-
url,
|
|
72
|
-
headers=headers,
|
|
73
|
-
params={"q": search_query, "per_page": min(per_page, 100)},
|
|
74
|
-
timeout=10.0,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
if response.status_code == 403:
|
|
78
|
-
# Rate limit exceeded
|
|
79
|
-
return Output(results=[], total_count=0)
|
|
80
|
-
|
|
81
|
-
response.raise_for_status()
|
|
82
|
-
data = response.json()
|
|
83
|
-
|
|
84
|
-
# Format results
|
|
85
|
-
results = []
|
|
86
|
-
for item in data.get("items", []):
|
|
87
|
-
results.append(
|
|
88
|
-
{
|
|
89
|
-
"name": item["name"],
|
|
90
|
-
"path": item["path"],
|
|
91
|
-
"repository": item["repository"]["full_name"],
|
|
92
|
-
"url": item["html_url"],
|
|
93
|
-
"score": item.get("score", 0.0),
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
return Output(results=results, total_count=data.get("total_count", 0))
|
|
98
|
-
|
|
99
|
-
except httpx.HTTPStatusError:
|
|
100
|
-
return Output(results=[], total_count=0)
|
|
101
|
-
except Exception:
|
|
102
|
-
return Output(results=[], total_count=0)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Export the function to be used as the tool
|
|
106
|
-
export = search
|