golf-mcp 0.1.11__py3-none-any.whl → 0.1.13__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 +209 -87
- golf/commands/__init__.py +1 -1
- golf/commands/build.py +31 -25
- golf/commands/init.py +81 -53
- golf/commands/run.py +30 -15
- golf/core/__init__.py +1 -1
- golf/core/builder.py +493 -362
- 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 +216 -95
- 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 +455 -310
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/METADATA +1 -1
- golf_mcp-0.1.13.dist-info/RECORD +55 -0
- golf_mcp-0.1.11.dist-info/RECORD +0 -55
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"""Welcome prompt for new users."""
|
|
2
2
|
|
|
3
|
-
from typing import List, Dict
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
async def welcome() -> List[Dict]:
|
|
4
|
+
async def welcome() -> list[dict]:
|
|
7
5
|
"""Provide a welcome prompt for new users.
|
|
8
|
-
|
|
6
|
+
|
|
9
7
|
This is a simple example prompt that demonstrates how to define
|
|
10
8
|
a prompt template in GolfMCP.
|
|
11
9
|
"""
|
|
@@ -15,16 +13,17 @@ async def welcome() -> List[Dict]:
|
|
|
15
13
|
"content": (
|
|
16
14
|
"You are an assistant for the {{project_name}} application. "
|
|
17
15
|
"You help users understand how to interact with this system and its capabilities."
|
|
18
|
-
)
|
|
16
|
+
),
|
|
19
17
|
},
|
|
20
18
|
{
|
|
21
19
|
"role": "user",
|
|
22
20
|
"content": (
|
|
23
21
|
"Welcome to {{project_name}}! This is a project built with GolfMCP. "
|
|
24
22
|
"How can I get started?"
|
|
25
|
-
)
|
|
23
|
+
),
|
|
26
24
|
},
|
|
27
25
|
]
|
|
28
26
|
|
|
27
|
+
|
|
29
28
|
# Designate the entry point function
|
|
30
|
-
export = welcome
|
|
29
|
+
export = welcome
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
"""Current time resource example."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
# The URI that clients will use to access this resource
|
|
7
7
|
resource_uri = "system://time/{format}"
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
async def current_time(format: str = "full") ->
|
|
10
|
+
async def current_time(format: str = "full") -> dict[str, Any]:
|
|
11
11
|
"""Provide the current time in various formats.
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
This is a simple resource example that accepts a format parameter.
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
Args:
|
|
16
16
|
format: The format to return ('full', 'iso', 'unix' or 'rfc')
|
|
17
17
|
"""
|
|
18
18
|
now = datetime.now()
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
# Prepare all possible formats
|
|
21
21
|
all_formats = {
|
|
22
22
|
"iso": now.isoformat(),
|
|
@@ -25,10 +25,10 @@ async def current_time(format: str = "full") -> Dict[str, Any]:
|
|
|
25
25
|
"formatted": {
|
|
26
26
|
"date": now.strftime("%Y-%m-%d"),
|
|
27
27
|
"time": now.strftime("%H:%M:%S"),
|
|
28
|
-
"timezone": now.astimezone().tzname()
|
|
29
|
-
}
|
|
28
|
+
"timezone": now.astimezone().tzname(),
|
|
29
|
+
},
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
# Return specific format or all formats
|
|
33
33
|
if format == "full":
|
|
34
34
|
return all_formats
|
|
@@ -37,5 +37,6 @@ async def current_time(format: str = "full") -> Dict[str, Any]:
|
|
|
37
37
|
else:
|
|
38
38
|
return {"error": f"Unknown format: {format}"}
|
|
39
39
|
|
|
40
|
+
|
|
40
41
|
# Designate the entry point function
|
|
41
|
-
export = current_time
|
|
42
|
+
export = current_time
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import platform
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
resource_uri = "info://system"
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
async def info() ->
|
|
10
|
+
async def info() -> dict[str, Any]:
|
|
11
11
|
"""Provide system information as a resource.
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
This is a simple example resource that demonstrates how to expose
|
|
14
14
|
data to an LLM client through the MCP protocol.
|
|
15
15
|
"""
|
|
@@ -20,8 +20,9 @@ async def info() -> Dict[str, Any]:
|
|
|
20
20
|
"system": platform.system(),
|
|
21
21
|
"python_version": platform.python_version(),
|
|
22
22
|
"architecture": platform.machine(),
|
|
23
|
-
}
|
|
23
|
+
},
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
|
|
26
27
|
# Designate the entry point function
|
|
27
|
-
export = info
|
|
28
|
+
export = info
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Weather shared functionality.
|
|
2
2
|
|
|
3
|
-
This common.py file demonstrates the recommended pattern for
|
|
3
|
+
This common.py file demonstrates the recommended pattern for
|
|
4
4
|
sharing functionality across multiple resources in a directory.
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -14,30 +14,36 @@ TEMPERATURE_UNIT = os.environ.get("WEATHER_TEMP_UNIT", "fahrenheit")
|
|
|
14
14
|
|
|
15
15
|
class WeatherApiClient:
|
|
16
16
|
"""Mock weather API client."""
|
|
17
|
-
|
|
18
|
-
def __init__(
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self, api_key: str = WEATHER_API_KEY, api_url: str = WEATHER_API_URL
|
|
20
|
+
) -> None:
|
|
19
21
|
self.api_key = api_key
|
|
20
22
|
self.api_url = api_url
|
|
21
23
|
self.unit = TEMPERATURE_UNIT
|
|
22
|
-
|
|
24
|
+
|
|
23
25
|
async def get_forecast(self, city: str, days: int = 3):
|
|
24
26
|
"""Get weather forecast for a city (mock implementation)."""
|
|
25
27
|
# This would make an API call in a real implementation
|
|
26
|
-
print(
|
|
28
|
+
print(
|
|
29
|
+
f"Would call {self.api_url}/forecast/{city} with API key {self.api_key[:4]}..."
|
|
30
|
+
)
|
|
27
31
|
return {
|
|
28
32
|
"city": city,
|
|
29
33
|
"unit": self.unit,
|
|
30
|
-
"forecast": [{"day": i, "temp": 70 + i} for i in range(days)]
|
|
34
|
+
"forecast": [{"day": i, "temp": 70 + i} for i in range(days)],
|
|
31
35
|
}
|
|
32
|
-
|
|
36
|
+
|
|
33
37
|
async def get_current(self, city: str):
|
|
34
38
|
"""Get current weather for a city (mock implementation)."""
|
|
35
|
-
print(
|
|
39
|
+
print(
|
|
40
|
+
f"Would call {self.api_url}/current/{city} with API key {self.api_key[:4]}..."
|
|
41
|
+
)
|
|
36
42
|
return {
|
|
37
43
|
"city": city,
|
|
38
44
|
"unit": self.unit,
|
|
39
45
|
"temperature": 72,
|
|
40
|
-
"conditions": "Sunny"
|
|
46
|
+
"conditions": "Sunny",
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
|
|
@@ -45,4 +51,4 @@ class WeatherApiClient:
|
|
|
45
51
|
weather_client = WeatherApiClient()
|
|
46
52
|
|
|
47
53
|
# This could also define shared models or other utilities
|
|
48
|
-
# that would be common across weather-related resources
|
|
54
|
+
# that would be common across weather-related resources
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""Current weather resource example."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
5
6
|
from .common import weather_client
|
|
6
7
|
|
|
7
8
|
# The URI that clients will use to access this resource
|
|
8
9
|
resource_uri = "weather://current/{city}"
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
async def current_weather(city: str) ->
|
|
12
|
+
async def current_weather(city: str) -> dict[str, Any]:
|
|
12
13
|
"""Provide current weather for the specified city.
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
This example demonstrates:
|
|
15
16
|
1. Nested resource organization (resources/weather/current.py)
|
|
16
17
|
2. Dynamic URI parameters (city in this case)
|
|
@@ -18,15 +19,18 @@ async def current_weather(city: str) -> Dict[str, Any]:
|
|
|
18
19
|
"""
|
|
19
20
|
# Use the shared weather client from common.py
|
|
20
21
|
weather_data = await weather_client.get_current(city)
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
# Add some additional data
|
|
23
|
-
weather_data.update(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
weather_data.update(
|
|
25
|
+
{
|
|
26
|
+
"time": datetime.now().isoformat(),
|
|
27
|
+
"source": "GolfMCP Weather API",
|
|
28
|
+
"unit": "fahrenheit",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
29
32
|
return weather_data
|
|
30
33
|
|
|
34
|
+
|
|
31
35
|
# Designate the entry point function
|
|
32
|
-
export = current_weather
|
|
36
|
+
export = current_weather
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""Weather forecast resource example demonstrating nested resources."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
5
6
|
from .common import weather_client
|
|
6
7
|
|
|
7
8
|
# The URI that clients will use to access this resource
|
|
8
9
|
resource_uri = "weather://forecast/{city}"
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
async def forecast_weather(city: str) ->
|
|
12
|
+
async def forecast_weather(city: str) -> dict[str, Any]:
|
|
12
13
|
"""Provide a weather forecast for the specified city.
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
This example demonstrates:
|
|
15
16
|
1. Nested resource organization (resources/weather/forecast.py)
|
|
16
17
|
2. Dynamic URI parameters (city in this case)
|
|
@@ -18,15 +19,18 @@ async def forecast_weather(city: str) -> Dict[str, Any]:
|
|
|
18
19
|
"""
|
|
19
20
|
# Use the shared weather client from common.py
|
|
20
21
|
forecast_data = await weather_client.get_forecast(city, days=5)
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
# Add some additional data
|
|
23
|
-
forecast_data.update(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
forecast_data.update(
|
|
25
|
+
{
|
|
26
|
+
"updated_at": datetime.now().isoformat(),
|
|
27
|
+
"source": "GolfMCP Weather API",
|
|
28
|
+
"unit": "fahrenheit",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
29
32
|
return forecast_data
|
|
30
33
|
|
|
34
|
+
|
|
31
35
|
# Designate the entry point function
|
|
32
|
-
export = forecast_weather
|
|
36
|
+
export = forecast_weather
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
"""Tool for fetching GitHub user information."""
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
4
|
-
from pydantic import BaseModel
|
|
5
3
|
import httpx
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
6
|
from golf.auth import get_provider_token
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class GitHubUserResponse(BaseModel):
|
|
10
10
|
"""Response model for GitHub user information."""
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
login: str
|
|
13
13
|
id: int
|
|
14
|
-
name:
|
|
15
|
-
email:
|
|
16
|
-
avatar_url:
|
|
17
|
-
location:
|
|
18
|
-
bio:
|
|
14
|
+
name: str | None = None
|
|
15
|
+
email: str | None = None
|
|
16
|
+
avatar_url: str | None = None
|
|
17
|
+
location: str | None = None
|
|
18
|
+
bio: str | None = None
|
|
19
19
|
public_repos: int = 0
|
|
20
20
|
followers: int = 0
|
|
21
21
|
following: int = 0
|
|
22
|
-
message:
|
|
22
|
+
message: str | None = None
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
async def get_github_user() -> GitHubUserResponse:
|
|
@@ -27,24 +27,24 @@ async def get_github_user() -> GitHubUserResponse:
|
|
|
27
27
|
try:
|
|
28
28
|
# Get GitHub token using our abstraction
|
|
29
29
|
github_token = get_provider_token()
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
if not github_token:
|
|
32
32
|
return GitHubUserResponse(
|
|
33
33
|
login="anonymous",
|
|
34
34
|
id=0,
|
|
35
|
-
message="Not authenticated. Please login first."
|
|
35
|
+
message="Not authenticated. Please login first.",
|
|
36
36
|
)
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
# Call GitHub API to get user info
|
|
39
39
|
async with httpx.AsyncClient() as client:
|
|
40
40
|
response = await client.get(
|
|
41
41
|
"https://api.github.com/user",
|
|
42
42
|
headers={
|
|
43
43
|
"Authorization": f"Bearer {github_token}",
|
|
44
|
-
"Accept": "application/vnd.github.v3+json"
|
|
45
|
-
}
|
|
44
|
+
"Accept": "application/vnd.github.v3+json",
|
|
45
|
+
},
|
|
46
46
|
)
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
if response.status_code == 200:
|
|
49
49
|
data = response.json()
|
|
50
50
|
return GitHubUserResponse(**data)
|
|
@@ -52,16 +52,14 @@ async def get_github_user() -> GitHubUserResponse:
|
|
|
52
52
|
return GitHubUserResponse(
|
|
53
53
|
login="error",
|
|
54
54
|
id=0,
|
|
55
|
-
message=f"GitHub API error: {response.status_code} - {response.text[:100]}"
|
|
55
|
+
message=f"GitHub API error: {response.status_code} - {response.text[:100]}",
|
|
56
56
|
)
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
except Exception as e:
|
|
59
59
|
return GitHubUserResponse(
|
|
60
|
-
login="error",
|
|
61
|
-
id=0,
|
|
62
|
-
message=f"Error fetching GitHub data: {str(e)}"
|
|
60
|
+
login="error", id=0, message=f"Error fetching GitHub data: {str(e)}"
|
|
63
61
|
)
|
|
64
62
|
|
|
65
63
|
|
|
66
64
|
# Export the tool
|
|
67
|
-
export = get_github_user
|
|
65
|
+
export = get_github_user
|
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
"""Hello World tool {{project_name}}."""
|
|
2
2
|
|
|
3
3
|
from typing import Annotated
|
|
4
|
+
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class Output(BaseModel):
|
|
8
9
|
"""Response from the hello tool."""
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
message: str
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
async def hello(
|
|
14
|
-
name: Annotated[
|
|
15
|
-
|
|
15
|
+
name: Annotated[
|
|
16
|
+
str, Field(description="The name of the person to greet")
|
|
17
|
+
] = "World",
|
|
18
|
+
greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello",
|
|
16
19
|
) -> Output:
|
|
17
20
|
"""Say hello to the given name.
|
|
18
|
-
|
|
21
|
+
|
|
19
22
|
This is a simple example tool that demonstrates the basic structure
|
|
20
23
|
of a tool implementation in GolfMCP.
|
|
21
24
|
"""
|
|
22
25
|
# The framework will add a context object automatically
|
|
23
26
|
# You can log using regular print during development
|
|
24
27
|
print(f"{greeting} {name}...")
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
# Create and return the response
|
|
27
30
|
return Output(message=f"{greeting}, {name}!")
|
|
28
31
|
|
|
32
|
+
|
|
29
33
|
# Designate the entry point function
|
|
30
|
-
export = hello
|
|
34
|
+
export = hello
|