golf-mcp 0.1.19__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.

Files changed (123) hide show
  1. golf/__init__.py +9 -1
  2. golf/_endpoints.py +6 -0
  3. golf/_endpoints_fallback.py +10 -0
  4. golf/auth/__init__.py +188 -84
  5. golf/auth/api_key.py +6 -14
  6. golf/auth/factory.py +333 -0
  7. golf/auth/helpers.py +12 -42
  8. golf/auth/providers.py +396 -0
  9. golf/auth/registry.py +256 -0
  10. golf/cli/branding.py +192 -0
  11. golf/cli/main.py +28 -69
  12. golf/commands/__init__.py +2 -0
  13. golf/commands/build.py +4 -7
  14. golf/commands/init.py +30 -53
  15. golf/commands/run.py +50 -20
  16. golf/core/builder.py +356 -412
  17. golf/core/builder_auth.py +63 -144
  18. golf/core/builder_telemetry.py +26 -3
  19. golf/core/config.py +38 -59
  20. golf/core/parser.py +132 -139
  21. golf/core/platform.py +12 -10
  22. golf/core/telemetry.py +11 -19
  23. golf/core/transformer.py +38 -15
  24. golf/examples/__pycache__/__init__.cpython-311.pyc +0 -0
  25. golf/examples/basic/.coverage +0 -0
  26. golf/examples/basic/.env.example +8 -4
  27. golf/examples/basic/README.md +117 -45
  28. golf/examples/basic/__pycache__/auth.cpython-311.pyc +0 -0
  29. golf/examples/basic/auth.py +76 -0
  30. golf/examples/basic/golf.json +2 -5
  31. golf/examples/basic/htmlcov/.gitignore +2 -0
  32. golf/examples/basic/htmlcov/class_index.html +547 -0
  33. golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +733 -0
  34. golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
  35. golf/examples/basic/htmlcov/function_index.html +2091 -0
  36. golf/examples/basic/htmlcov/index.html +349 -0
  37. golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
  38. golf/examples/basic/htmlcov/status.json +1 -0
  39. golf/examples/basic/htmlcov/style_cb_8e611ae1.css +337 -0
  40. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +323 -0
  41. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +170 -0
  42. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +430 -0
  43. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +288 -0
  44. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +493 -0
  45. golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +353 -0
  46. golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +120 -0
  47. golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +1535 -0
  48. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +98 -0
  49. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +289 -0
  50. golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +476 -0
  51. golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +97 -0
  52. golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +102 -0
  53. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +178 -0
  54. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +387 -0
  55. golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +222 -0
  56. golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +106 -0
  57. golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +107 -0
  58. golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +98 -0
  59. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +306 -0
  60. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +329 -0
  61. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +1471 -0
  62. golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +186 -0
  63. golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +315 -0
  64. golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +1149 -0
  65. golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +279 -0
  66. golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +589 -0
  67. golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +286 -0
  68. golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +107 -0
  69. golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +417 -0
  70. golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +109 -0
  71. golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +109 -0
  72. golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +150 -0
  73. golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +267 -0
  74. golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +318 -0
  75. golf/examples/basic/prompts/__pycache__/welcome.cpython-311.pyc +0 -0
  76. golf/examples/basic/prompts/welcome.py +3 -5
  77. golf/examples/basic/resources/__pycache__/current_time.cpython-311.pyc +0 -0
  78. golf/examples/basic/resources/__pycache__/info.cpython-311.pyc +0 -0
  79. golf/examples/basic/resources/current_time.py +5 -13
  80. golf/examples/basic/resources/weather/__pycache__/common.cpython-311.pyc +0 -0
  81. golf/examples/basic/resources/weather/__pycache__/current.cpython-311.pyc +0 -0
  82. golf/examples/basic/resources/weather/__pycache__/forecast.cpython-311.pyc +0 -0
  83. golf/examples/basic/resources/weather/city.py +46 -0
  84. golf/examples/basic/resources/weather/common.py +4 -11
  85. golf/examples/basic/resources/weather/current.py +5 -5
  86. golf/examples/basic/resources/weather/forecast.py +5 -5
  87. golf/examples/basic/tools/__pycache__/calculator.cpython-311.pyc +0 -0
  88. golf/examples/basic/tools/calculator.py +94 -0
  89. golf/examples/basic/tools/say/__pycache__/hello.cpython-311.pyc +0 -0
  90. golf/examples/basic/tools/say/hello.py +65 -0
  91. golf/metrics/collector.py +100 -19
  92. golf/telemetry/__init__.py +4 -0
  93. golf/telemetry/instrumentation.py +496 -174
  94. golf/utilities/__init__.py +12 -0
  95. golf/utilities/context.py +53 -0
  96. golf/utilities/elicitation.py +170 -0
  97. golf/utilities/sampling.py +221 -0
  98. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/METADATA +56 -110
  99. golf_mcp-0.2.0.dist-info/RECORD +110 -0
  100. golf/auth/oauth.py +0 -861
  101. golf/auth/provider.py +0 -115
  102. golf/examples/api_key/.env +0 -2
  103. golf/examples/api_key/.env.example +0 -1
  104. golf/examples/api_key/README.md +0 -84
  105. golf/examples/api_key/golf.json +0 -8
  106. golf/examples/api_key/pre_build.py +0 -11
  107. golf/examples/api_key/tools/issues/create.py +0 -93
  108. golf/examples/api_key/tools/issues/list.py +0 -92
  109. golf/examples/api_key/tools/repos/list.py +0 -111
  110. golf/examples/api_key/tools/search/code.py +0 -106
  111. golf/examples/api_key/tools/users/get.py +0 -82
  112. golf/examples/basic/.env +0 -5
  113. golf/examples/basic/pre_build.py +0 -28
  114. golf/examples/basic/tools/github_user.py +0 -65
  115. golf/examples/basic/tools/hello.py +0 -34
  116. golf/examples/basic/tools/payments/charge.py +0 -70
  117. golf/examples/basic/tools/payments/common.py +0 -36
  118. golf/examples/basic/tools/payments/refund.py +0 -61
  119. golf_mcp-0.1.19.dist-info/RECORD +0 -60
  120. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/WHEEL +0 -0
  121. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
  122. {golf_mcp-0.1.19.dist-info → golf_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
  123. {golf_mcp-0.1.19.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()
@@ -1,2 +0,0 @@
1
- OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318/v1/traces"
2
- OTEL_SERVICE_NAME="golf-mcp"
@@ -1 +0,0 @@
1
- #GOLF_API_KEY=<your-api-key>
@@ -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
@@ -1,8 +0,0 @@
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
- "opentelemetry_enabled": false
8
- }
@@ -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