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.

Files changed (48) hide show
  1. golf/__init__.py +1 -1
  2. golf/auth/__init__.py +38 -26
  3. golf/auth/api_key.py +16 -23
  4. golf/auth/helpers.py +68 -54
  5. golf/auth/oauth.py +340 -277
  6. golf/auth/provider.py +58 -53
  7. golf/cli/__init__.py +1 -1
  8. golf/cli/main.py +209 -87
  9. golf/commands/__init__.py +1 -1
  10. golf/commands/build.py +31 -25
  11. golf/commands/init.py +81 -53
  12. golf/commands/run.py +30 -15
  13. golf/core/__init__.py +1 -1
  14. golf/core/builder.py +493 -362
  15. golf/core/builder_auth.py +115 -107
  16. golf/core/builder_telemetry.py +12 -9
  17. golf/core/config.py +62 -46
  18. golf/core/parser.py +174 -136
  19. golf/core/telemetry.py +216 -95
  20. golf/core/transformer.py +53 -55
  21. golf/examples/__init__.py +0 -1
  22. golf/examples/api_key/pre_build.py +2 -2
  23. golf/examples/api_key/tools/issues/create.py +35 -36
  24. golf/examples/api_key/tools/issues/list.py +42 -37
  25. golf/examples/api_key/tools/repos/list.py +50 -29
  26. golf/examples/api_key/tools/search/code.py +50 -37
  27. golf/examples/api_key/tools/users/get.py +21 -20
  28. golf/examples/basic/pre_build.py +4 -4
  29. golf/examples/basic/prompts/welcome.py +6 -7
  30. golf/examples/basic/resources/current_time.py +10 -9
  31. golf/examples/basic/resources/info.py +6 -5
  32. golf/examples/basic/resources/weather/common.py +16 -10
  33. golf/examples/basic/resources/weather/current.py +15 -11
  34. golf/examples/basic/resources/weather/forecast.py +15 -11
  35. golf/examples/basic/tools/github_user.py +19 -21
  36. golf/examples/basic/tools/hello.py +10 -6
  37. golf/examples/basic/tools/payments/charge.py +34 -25
  38. golf/examples/basic/tools/payments/common.py +8 -6
  39. golf/examples/basic/tools/payments/refund.py +29 -25
  40. golf/telemetry/__init__.py +6 -6
  41. golf/telemetry/instrumentation.py +455 -310
  42. {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/METADATA +1 -1
  43. golf_mcp-0.1.13.dist-info/RECORD +55 -0
  44. golf_mcp-0.1.11.dist-info/RECORD +0 -55
  45. {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/WHEEL +0 -0
  46. {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/entry_points.txt +0 -0
  47. {golf_mcp-0.1.11.dist-info → golf_mcp-0.1.13.dist-info}/licenses/LICENSE +0 -0
  48. {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, List, Optional, Dict, Any
4
- from pydantic import BaseModel, Field
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
- results: List[Dict[str, Any]]
13
+
14
+ results: list[dict[str, Any]]
13
15
  total_count: int
14
16
 
15
17
 
16
18
  async def search(
17
- query: Annotated[str, Field(description="Search query (e.g., 'addClass', 'TODO', etc.)")],
18
- language: Annotated[Optional[str], Field(description="Filter by programming language (e.g., 'python', 'javascript')")] = None,
19
- repo: Annotated[Optional[str], Field(description="Search within a specific repository (format: 'owner/repo')")] = None,
20
- org: Annotated[Optional[str], Field(description="Search within repositories of a specific organization")] = None,
21
- per_page: Annotated[int, Field(description="Number of results per page (max 100)")] = 10
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
- "q": search_query,
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
- "name": item["name"],
75
- "path": item["path"],
76
- "repository": item["repository"]["full_name"],
77
- "url": item["html_url"],
78
- "score": item.get("score", 0.0)
79
- })
80
-
81
- return Output(
82
- results=results,
83
- total_count=data.get("total_count", 0)
84
- )
85
-
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, Optional, Dict, Any
4
- from pydantic import BaseModel, Field
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: Optional[Dict[str, Any]] = None
15
+ user: dict[str, Any] | None = None
14
16
 
15
17
 
16
18
  async def get(
17
- username: Annotated[Optional[str], Field(description="GitHub username (if not provided, gets authenticated user)")] = None
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
- url,
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
@@ -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", # This should be your Golf server's accessible URL
20
- callback_path="/auth/callback", # Golf's callback path
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 Dict, Any
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") -> Dict[str, Any]:
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 Dict, Any
5
+ from typing import Any
6
6
 
7
7
  resource_uri = "info://system"
8
8
 
9
9
 
10
- async def info() -> Dict[str, Any]:
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__(self, api_key: str = WEATHER_API_KEY, api_url: str = WEATHER_API_URL):
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(f"Would call {self.api_url}/forecast/{city} with API key {self.api_key[:4]}...")
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(f"Would call {self.api_url}/current/{city} with API key {self.api_key[:4]}...")
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 Dict, Any
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) -> Dict[str, Any]:
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
- "time": datetime.now().isoformat(),
25
- "source": "GolfMCP Weather API",
26
- "unit": "fahrenheit"
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 Dict, Any
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) -> Dict[str, Any]:
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
- "updated_at": datetime.now().isoformat(),
25
- "source": "GolfMCP Weather API",
26
- "unit": "fahrenheit"
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: Optional[str] = None
15
- email: Optional[str] = None
16
- avatar_url: Optional[str] = None
17
- location: Optional[str] = None
18
- bio: Optional[str] = None
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: Optional[str] = None
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[str, Field(description="The name of the person to greet")] = "World",
15
- greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello"
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