golf-mcp 0.1.10__py3-none-any.whl → 0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of golf-mcp might be problematic. Click here for more details.
- golf/__init__.py +1 -1
- golf/auth/__init__.py +38 -26
- golf/auth/api_key.py +16 -23
- golf/auth/helpers.py +68 -54
- golf/auth/oauth.py +340 -277
- golf/auth/provider.py +58 -53
- golf/cli/__init__.py +1 -1
- golf/cli/main.py +202 -82
- golf/commands/__init__.py +1 -1
- golf/commands/build.py +31 -25
- golf/commands/init.py +119 -80
- golf/commands/run.py +14 -13
- golf/core/__init__.py +1 -1
- golf/core/builder.py +478 -353
- golf/core/builder_auth.py +115 -107
- golf/core/builder_telemetry.py +12 -9
- golf/core/config.py +62 -46
- golf/core/parser.py +174 -136
- golf/core/telemetry.py +169 -69
- golf/core/transformer.py +53 -55
- golf/examples/__init__.py +0 -1
- golf/examples/api_key/pre_build.py +2 -2
- golf/examples/api_key/tools/issues/create.py +35 -36
- golf/examples/api_key/tools/issues/list.py +42 -37
- golf/examples/api_key/tools/repos/list.py +50 -29
- golf/examples/api_key/tools/search/code.py +50 -37
- golf/examples/api_key/tools/users/get.py +21 -20
- golf/examples/basic/pre_build.py +4 -4
- golf/examples/basic/prompts/welcome.py +6 -7
- golf/examples/basic/resources/current_time.py +10 -9
- golf/examples/basic/resources/info.py +6 -5
- golf/examples/basic/resources/weather/common.py +16 -10
- golf/examples/basic/resources/weather/current.py +15 -11
- golf/examples/basic/resources/weather/forecast.py +15 -11
- golf/examples/basic/tools/github_user.py +19 -21
- golf/examples/basic/tools/hello.py +10 -6
- golf/examples/basic/tools/payments/charge.py +34 -25
- golf/examples/basic/tools/payments/common.py +8 -6
- golf/examples/basic/tools/payments/refund.py +29 -25
- golf/telemetry/__init__.py +6 -6
- golf/telemetry/instrumentation.py +781 -276
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/METADATA +1 -1
- golf_mcp-0.1.12.dist-info/RECORD +55 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/WHEEL +1 -1
- golf_mcp-0.1.10.dist-info/RECORD +0 -55
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/top_level.txt +0 -0
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
"""Create a new issue in a GitHub repository."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated
|
|
4
|
-
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
5
|
import httpx
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
6
7
|
|
|
7
8
|
from golf.auth import get_api_key
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Output(BaseModel):
|
|
11
12
|
"""Response from creating an issue."""
|
|
13
|
+
|
|
12
14
|
success: bool
|
|
13
|
-
issue_number:
|
|
14
|
-
issue_url:
|
|
15
|
-
error:
|
|
15
|
+
issue_number: int | None = None
|
|
16
|
+
issue_url: str | None = None
|
|
17
|
+
error: str | None = None
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
async def create(
|
|
19
21
|
repo: Annotated[str, Field(description="Repository name in format 'owner/repo'")],
|
|
20
22
|
title: Annotated[str, Field(description="Issue title")],
|
|
21
|
-
body: Annotated[
|
|
22
|
-
|
|
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,
|
|
23
29
|
) -> Output:
|
|
24
30
|
"""Create a new issue.
|
|
25
|
-
|
|
31
|
+
|
|
26
32
|
Requires authentication with appropriate permissions.
|
|
27
33
|
"""
|
|
28
34
|
github_token = get_api_key()
|
|
29
|
-
|
|
35
|
+
|
|
30
36
|
if not github_token:
|
|
31
37
|
return Output(
|
|
32
38
|
success=False,
|
|
33
|
-
error="Authentication required. Please provide a GitHub token."
|
|
39
|
+
error="Authentication required. Please provide a GitHub token.",
|
|
34
40
|
)
|
|
35
|
-
|
|
41
|
+
|
|
36
42
|
# Validate repo format
|
|
37
|
-
if
|
|
38
|
-
return Output(
|
|
39
|
-
|
|
40
|
-
error="Repository must be in format 'owner/repo'"
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
+
if "/" not in repo:
|
|
44
|
+
return Output(success=False, error="Repository must be in format 'owner/repo'")
|
|
45
|
+
|
|
43
46
|
url = f"https://api.github.com/repos/{repo}/issues"
|
|
44
|
-
|
|
47
|
+
|
|
45
48
|
# Build request payload
|
|
46
|
-
payload = {
|
|
47
|
-
"title": title,
|
|
48
|
-
"body": body
|
|
49
|
-
}
|
|
49
|
+
payload = {"title": title, "body": body}
|
|
50
50
|
if labels:
|
|
51
51
|
payload["labels"] = labels
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
try:
|
|
54
54
|
async with httpx.AsyncClient() as client:
|
|
55
55
|
response = await client.post(
|
|
@@ -57,38 +57,37 @@ async def create(
|
|
|
57
57
|
headers={
|
|
58
58
|
"Authorization": f"Bearer {github_token}",
|
|
59
59
|
"Accept": "application/vnd.github.v3+json",
|
|
60
|
-
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
60
|
+
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
61
61
|
},
|
|
62
62
|
json=payload,
|
|
63
|
-
timeout=10.0
|
|
63
|
+
timeout=10.0,
|
|
64
64
|
)
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
response.raise_for_status()
|
|
67
67
|
issue_data = response.json()
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
return Output(
|
|
70
70
|
success=True,
|
|
71
71
|
issue_number=issue_data["number"],
|
|
72
|
-
issue_url=issue_data["html_url"]
|
|
72
|
+
issue_url=issue_data["html_url"],
|
|
73
73
|
)
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
except httpx.HTTPStatusError as e:
|
|
76
76
|
error_messages = {
|
|
77
77
|
401: "Invalid or missing authentication token",
|
|
78
78
|
403: "Insufficient permissions to create issues in this repository",
|
|
79
79
|
404: "Repository not found",
|
|
80
|
-
422: "Invalid request data"
|
|
80
|
+
422: "Invalid request data",
|
|
81
81
|
}
|
|
82
82
|
return Output(
|
|
83
83
|
success=False,
|
|
84
|
-
error=error_messages.get(
|
|
84
|
+
error=error_messages.get(
|
|
85
|
+
e.response.status_code, f"GitHub API error: {e.response.status_code}"
|
|
86
|
+
),
|
|
85
87
|
)
|
|
86
88
|
except Exception as e:
|
|
87
|
-
return Output(
|
|
88
|
-
success=False,
|
|
89
|
-
error=f"Failed to create issue: {str(e)}"
|
|
90
|
-
)
|
|
89
|
+
return Output(success=False, error=f"Failed to create issue: {str(e)}")
|
|
91
90
|
|
|
92
91
|
|
|
93
92
|
# Export the function to be used as the tool
|
|
94
|
-
export = create
|
|
93
|
+
export = create
|
|
@@ -1,87 +1,92 @@
|
|
|
1
1
|
"""List issues in a GitHub repository."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated,
|
|
4
|
-
|
|
3
|
+
from typing import Annotated, Any
|
|
4
|
+
|
|
5
5
|
import httpx
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
6
7
|
|
|
7
8
|
from golf.auth import get_api_key
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Output(BaseModel):
|
|
11
12
|
"""List of issues from the repository."""
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
issues: list[dict[str, Any]]
|
|
13
15
|
total_count: int
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
async def list(
|
|
17
19
|
repo: Annotated[str, Field(description="Repository name in format 'owner/repo'")],
|
|
18
|
-
state: Annotated[
|
|
19
|
-
|
|
20
|
-
|
|
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,
|
|
21
30
|
) -> Output:
|
|
22
31
|
"""List issues in a repository.
|
|
23
|
-
|
|
32
|
+
|
|
24
33
|
Returns issues with their number, title, state, and other metadata.
|
|
25
34
|
Pull requests are filtered out from the results.
|
|
26
35
|
"""
|
|
27
36
|
github_token = get_api_key()
|
|
28
|
-
|
|
37
|
+
|
|
29
38
|
# Validate repo format
|
|
30
|
-
if
|
|
39
|
+
if "/" not in repo:
|
|
31
40
|
return Output(issues=[], total_count=0)
|
|
32
|
-
|
|
41
|
+
|
|
33
42
|
url = f"https://api.github.com/repos/{repo}/issues"
|
|
34
|
-
|
|
43
|
+
|
|
35
44
|
# Prepare headers
|
|
36
45
|
headers = {
|
|
37
46
|
"Accept": "application/vnd.github.v3+json",
|
|
38
|
-
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
47
|
+
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
39
48
|
}
|
|
40
49
|
if github_token:
|
|
41
50
|
headers["Authorization"] = f"Bearer {github_token}"
|
|
42
|
-
|
|
51
|
+
|
|
43
52
|
# Build query parameters
|
|
44
|
-
params = {
|
|
45
|
-
"state": state,
|
|
46
|
-
"per_page": min(per_page, 100)
|
|
47
|
-
}
|
|
53
|
+
params = {"state": state, "per_page": min(per_page, 100)}
|
|
48
54
|
if labels:
|
|
49
55
|
params["labels"] = labels
|
|
50
|
-
|
|
56
|
+
|
|
51
57
|
try:
|
|
52
58
|
async with httpx.AsyncClient() as client:
|
|
53
59
|
response = await client.get(
|
|
54
|
-
url,
|
|
55
|
-
headers=headers,
|
|
56
|
-
params=params,
|
|
57
|
-
timeout=10.0
|
|
60
|
+
url, headers=headers, params=params, timeout=10.0
|
|
58
61
|
)
|
|
59
|
-
|
|
62
|
+
|
|
60
63
|
response.raise_for_status()
|
|
61
64
|
issues_data = response.json()
|
|
62
|
-
|
|
65
|
+
|
|
63
66
|
# Filter out pull requests and format issues
|
|
64
67
|
issues = []
|
|
65
68
|
for issue in issues_data:
|
|
66
69
|
# Skip pull requests (they appear in issues endpoint too)
|
|
67
70
|
if "pull_request" in issue:
|
|
68
71
|
continue
|
|
69
|
-
|
|
70
|
-
issues.append(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
+
|
|
80
85
|
return Output(issues=issues, total_count=len(issues))
|
|
81
|
-
|
|
86
|
+
|
|
82
87
|
except Exception:
|
|
83
88
|
return Output(issues=[], total_count=0)
|
|
84
89
|
|
|
85
90
|
|
|
86
91
|
# Export the function to be used as the tool
|
|
87
|
-
export = list
|
|
92
|
+
export = list
|
|
@@ -1,31 +1,50 @@
|
|
|
1
1
|
"""List GitHub repositories for a user or organization."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated,
|
|
4
|
-
|
|
3
|
+
from typing import Annotated, Any
|
|
4
|
+
|
|
5
5
|
import httpx
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
6
7
|
|
|
7
8
|
from golf.auth import get_api_key
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Output(BaseModel):
|
|
11
12
|
"""List of repositories."""
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
repositories: list[dict[str, Any]]
|
|
13
15
|
total_count: int
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
async def list(
|
|
17
|
-
username: Annotated[
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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,
|
|
21
40
|
) -> Output:
|
|
22
41
|
"""List GitHub repositories.
|
|
23
|
-
|
|
42
|
+
|
|
24
43
|
If neither username nor org is provided, lists repositories for the authenticated user.
|
|
25
44
|
"""
|
|
26
45
|
# Get the GitHub token from the request context
|
|
27
46
|
github_token = get_api_key()
|
|
28
|
-
|
|
47
|
+
|
|
29
48
|
# Determine the API endpoint
|
|
30
49
|
if org:
|
|
31
50
|
url = f"https://api.github.com/orgs/{org}/repos"
|
|
@@ -36,15 +55,15 @@ async def list(
|
|
|
36
55
|
if not github_token:
|
|
37
56
|
return Output(repositories=[], total_count=0)
|
|
38
57
|
url = "https://api.github.com/user/repos"
|
|
39
|
-
|
|
58
|
+
|
|
40
59
|
# Prepare headers
|
|
41
60
|
headers = {
|
|
42
61
|
"Accept": "application/vnd.github.v3+json",
|
|
43
|
-
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
62
|
+
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
44
63
|
}
|
|
45
64
|
if github_token:
|
|
46
65
|
headers["Authorization"] = f"Bearer {github_token}"
|
|
47
|
-
|
|
66
|
+
|
|
48
67
|
try:
|
|
49
68
|
async with httpx.AsyncClient() as client:
|
|
50
69
|
response = await client.get(
|
|
@@ -53,30 +72,32 @@ async def list(
|
|
|
53
72
|
params={
|
|
54
73
|
"sort": sort,
|
|
55
74
|
"per_page": min(per_page, 100),
|
|
56
|
-
"type": "all" if not username and not org else None
|
|
75
|
+
"type": "all" if not username and not org else None,
|
|
57
76
|
},
|
|
58
|
-
timeout=10.0
|
|
77
|
+
timeout=10.0,
|
|
59
78
|
)
|
|
60
|
-
|
|
79
|
+
|
|
61
80
|
response.raise_for_status()
|
|
62
81
|
repos_data = response.json()
|
|
63
|
-
|
|
82
|
+
|
|
64
83
|
# Format repositories
|
|
65
84
|
repositories = []
|
|
66
85
|
for repo in repos_data:
|
|
67
|
-
repositories.append(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
|
|
78
99
|
return Output(repositories=repositories, total_count=len(repositories))
|
|
79
|
-
|
|
100
|
+
|
|
80
101
|
except httpx.HTTPStatusError as e:
|
|
81
102
|
if e.response.status_code in [401, 404]:
|
|
82
103
|
return Output(repositories=[], total_count=0)
|
|
@@ -87,4 +108,4 @@ async def list(
|
|
|
87
108
|
|
|
88
109
|
|
|
89
110
|
# Export the function to be used as the tool
|
|
90
|
-
export = list
|
|
111
|
+
export = list
|
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
"""Search GitHub code."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated,
|
|
4
|
-
|
|
3
|
+
from typing import Annotated, Any
|
|
4
|
+
|
|
5
5
|
import httpx
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
6
7
|
|
|
7
8
|
from golf.auth import get_api_key
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Output(BaseModel):
|
|
11
12
|
"""Code search results."""
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
results: list[dict[str, Any]]
|
|
13
15
|
total_count: int
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
async def search(
|
|
17
|
-
query: Annotated[
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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,
|
|
22
39
|
) -> Output:
|
|
23
40
|
"""Search for code on GitHub.
|
|
24
|
-
|
|
41
|
+
|
|
25
42
|
Without authentication, you're limited to 10 requests per minute.
|
|
26
43
|
With authentication, you can make up to 30 requests per minute.
|
|
27
44
|
"""
|
|
28
45
|
github_token = get_api_key()
|
|
29
|
-
|
|
46
|
+
|
|
30
47
|
# Build the search query
|
|
31
48
|
search_parts = [query]
|
|
32
49
|
if language:
|
|
@@ -35,54 +52,50 @@ async def search(
|
|
|
35
52
|
search_parts.append(f"repo:{repo}")
|
|
36
53
|
if org:
|
|
37
54
|
search_parts.append(f"org:{org}")
|
|
38
|
-
|
|
55
|
+
|
|
39
56
|
search_query = " ".join(search_parts)
|
|
40
|
-
|
|
57
|
+
|
|
41
58
|
url = "https://api.github.com/search/code"
|
|
42
|
-
|
|
59
|
+
|
|
43
60
|
# Prepare headers
|
|
44
61
|
headers = {
|
|
45
62
|
"Accept": "application/vnd.github.v3+json",
|
|
46
|
-
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
63
|
+
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
47
64
|
}
|
|
48
65
|
if github_token:
|
|
49
66
|
headers["Authorization"] = f"Bearer {github_token}"
|
|
50
|
-
|
|
67
|
+
|
|
51
68
|
try:
|
|
52
69
|
async with httpx.AsyncClient() as client:
|
|
53
70
|
response = await client.get(
|
|
54
71
|
url,
|
|
55
72
|
headers=headers,
|
|
56
|
-
params={
|
|
57
|
-
|
|
58
|
-
"per_page": min(per_page, 100)
|
|
59
|
-
},
|
|
60
|
-
timeout=10.0
|
|
73
|
+
params={"q": search_query, "per_page": min(per_page, 100)},
|
|
74
|
+
timeout=10.0,
|
|
61
75
|
)
|
|
62
|
-
|
|
76
|
+
|
|
63
77
|
if response.status_code == 403:
|
|
64
78
|
# Rate limit exceeded
|
|
65
79
|
return Output(results=[], total_count=0)
|
|
66
|
-
|
|
80
|
+
|
|
67
81
|
response.raise_for_status()
|
|
68
82
|
data = response.json()
|
|
69
|
-
|
|
83
|
+
|
|
70
84
|
# Format results
|
|
71
85
|
results = []
|
|
72
86
|
for item in data.get("items", []):
|
|
73
|
-
results.append(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
|
|
86
99
|
except httpx.HTTPStatusError:
|
|
87
100
|
return Output(results=[], total_count=0)
|
|
88
101
|
except Exception:
|
|
@@ -90,4 +103,4 @@ async def search(
|
|
|
90
103
|
|
|
91
104
|
|
|
92
105
|
# Export the function to be used as the tool
|
|
93
|
-
export = search
|
|
106
|
+
export = search
|
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
"""Get GitHub user information."""
|
|
2
2
|
|
|
3
|
-
from typing import Annotated,
|
|
4
|
-
|
|
3
|
+
from typing import Annotated, Any
|
|
4
|
+
|
|
5
5
|
import httpx
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
6
7
|
|
|
7
8
|
from golf.auth import get_api_key
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Output(BaseModel):
|
|
11
12
|
"""User information result."""
|
|
13
|
+
|
|
12
14
|
found: bool
|
|
13
|
-
user:
|
|
15
|
+
user: dict[str, Any] | None = None
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
async def get(
|
|
17
|
-
username: Annotated[
|
|
19
|
+
username: Annotated[
|
|
20
|
+
str | None,
|
|
21
|
+
Field(description="GitHub username (if not provided, gets authenticated user)"),
|
|
22
|
+
] = None,
|
|
18
23
|
) -> Output:
|
|
19
24
|
"""Get information about a GitHub user.
|
|
20
|
-
|
|
25
|
+
|
|
21
26
|
If no username is provided, returns information about the authenticated user.
|
|
22
27
|
This is useful for testing if authentication is working correctly.
|
|
23
28
|
"""
|
|
24
29
|
github_token = get_api_key()
|
|
25
|
-
|
|
30
|
+
|
|
26
31
|
# Determine the API endpoint
|
|
27
32
|
if username:
|
|
28
33
|
url = f"https://api.github.com/users/{username}"
|
|
@@ -31,26 +36,22 @@ async def get(
|
|
|
31
36
|
if not github_token:
|
|
32
37
|
return Output(found=False)
|
|
33
38
|
url = "https://api.github.com/user"
|
|
34
|
-
|
|
39
|
+
|
|
35
40
|
# Prepare headers
|
|
36
41
|
headers = {
|
|
37
42
|
"Accept": "application/vnd.github.v3+json",
|
|
38
|
-
"User-Agent": "Golf-GitHub-MCP-Server"
|
|
43
|
+
"User-Agent": "Golf-GitHub-MCP-Server",
|
|
39
44
|
}
|
|
40
45
|
if github_token:
|
|
41
46
|
headers["Authorization"] = f"Bearer {github_token}"
|
|
42
|
-
|
|
47
|
+
|
|
43
48
|
try:
|
|
44
49
|
async with httpx.AsyncClient() as client:
|
|
45
|
-
response = await client.get(
|
|
46
|
-
|
|
47
|
-
headers=headers,
|
|
48
|
-
timeout=10.0
|
|
49
|
-
)
|
|
50
|
-
|
|
50
|
+
response = await client.get(url, headers=headers, timeout=10.0)
|
|
51
|
+
|
|
51
52
|
response.raise_for_status()
|
|
52
53
|
user_data = response.json()
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
return Output(
|
|
55
56
|
found=True,
|
|
56
57
|
user={
|
|
@@ -64,10 +65,10 @@ async def get(
|
|
|
64
65
|
"followers": user_data.get("followers", 0),
|
|
65
66
|
"following": user_data.get("following", 0),
|
|
66
67
|
"created_at": user_data["created_at"],
|
|
67
|
-
"url": user_data["html_url"]
|
|
68
|
-
}
|
|
68
|
+
"url": user_data["html_url"],
|
|
69
|
+
},
|
|
69
70
|
)
|
|
70
|
-
|
|
71
|
+
|
|
71
72
|
except httpx.HTTPStatusError as e:
|
|
72
73
|
if e.response.status_code in [401, 404]:
|
|
73
74
|
return Output(found=False)
|
|
@@ -78,4 +79,4 @@ async def get(
|
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
# Export the function to be used as the tool
|
|
81
|
-
export = get
|
|
82
|
+
export = get
|
golf/examples/basic/pre_build.py
CHANGED
|
@@ -16,13 +16,13 @@ github_provider = ProviderConfig(
|
|
|
16
16
|
token_url="https://github.com/login/oauth/access_token",
|
|
17
17
|
userinfo_url="https://api.github.com/user",
|
|
18
18
|
scopes=["read:user", "user:email"],
|
|
19
|
-
issuer_url="http://127.0.0.1:3000",
|
|
20
|
-
callback_path="/auth/callback",
|
|
21
|
-
token_expiration=3600 # 1 hour
|
|
19
|
+
issuer_url="http://127.0.0.1:3000", # This should be your Golf server's accessible URL
|
|
20
|
+
callback_path="/auth/callback", # Golf's callback path
|
|
21
|
+
token_expiration=3600, # 1 hour
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
# Configure authentication with the provider
|
|
25
25
|
configure_auth(
|
|
26
26
|
provider=github_provider,
|
|
27
27
|
required_scopes=["read:user"], # Require read:user scope for protected endpoints
|
|
28
|
-
)
|
|
28
|
+
)
|