golf-mcp 0.2.16__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.
Files changed (52) hide show
  1. golf/__init__.py +1 -0
  2. golf/auth/__init__.py +277 -0
  3. golf/auth/api_key.py +73 -0
  4. golf/auth/factory.py +360 -0
  5. golf/auth/helpers.py +175 -0
  6. golf/auth/providers.py +586 -0
  7. golf/auth/registry.py +256 -0
  8. golf/cli/__init__.py +1 -0
  9. golf/cli/branding.py +191 -0
  10. golf/cli/main.py +377 -0
  11. golf/commands/__init__.py +5 -0
  12. golf/commands/build.py +81 -0
  13. golf/commands/init.py +290 -0
  14. golf/commands/run.py +137 -0
  15. golf/core/__init__.py +1 -0
  16. golf/core/builder.py +1884 -0
  17. golf/core/builder_auth.py +209 -0
  18. golf/core/builder_metrics.py +221 -0
  19. golf/core/builder_telemetry.py +99 -0
  20. golf/core/config.py +199 -0
  21. golf/core/parser.py +1085 -0
  22. golf/core/telemetry.py +492 -0
  23. golf/core/transformer.py +231 -0
  24. golf/examples/__init__.py +0 -0
  25. golf/examples/basic/.env.example +4 -0
  26. golf/examples/basic/README.md +133 -0
  27. golf/examples/basic/auth.py +76 -0
  28. golf/examples/basic/golf.json +5 -0
  29. golf/examples/basic/prompts/welcome.py +27 -0
  30. golf/examples/basic/resources/current_time.py +34 -0
  31. golf/examples/basic/resources/info.py +28 -0
  32. golf/examples/basic/resources/weather/city.py +46 -0
  33. golf/examples/basic/resources/weather/client.py +48 -0
  34. golf/examples/basic/resources/weather/current.py +36 -0
  35. golf/examples/basic/resources/weather/forecast.py +36 -0
  36. golf/examples/basic/tools/calculator.py +94 -0
  37. golf/examples/basic/tools/say/hello.py +65 -0
  38. golf/metrics/__init__.py +10 -0
  39. golf/metrics/collector.py +320 -0
  40. golf/metrics/registry.py +12 -0
  41. golf/telemetry/__init__.py +23 -0
  42. golf/telemetry/instrumentation.py +1402 -0
  43. golf/utilities/__init__.py +12 -0
  44. golf/utilities/context.py +53 -0
  45. golf/utilities/elicitation.py +170 -0
  46. golf/utilities/sampling.py +221 -0
  47. golf_mcp-0.2.16.dist-info/METADATA +262 -0
  48. golf_mcp-0.2.16.dist-info/RECORD +52 -0
  49. golf_mcp-0.2.16.dist-info/WHEEL +5 -0
  50. golf_mcp-0.2.16.dist-info/entry_points.txt +2 -0
  51. golf_mcp-0.2.16.dist-info/licenses/LICENSE +201 -0
  52. golf_mcp-0.2.16.dist-info/top_level.txt +1 -0
@@ -0,0 +1,231 @@
1
+ """Transform GolfMCP components into standalone FastMCP code.
2
+
3
+ This module provides utilities for transforming GolfMCP's convention-based code
4
+ into explicit FastMCP component registrations.
5
+ """
6
+
7
+ import ast
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from golf.core.parser import ParsedComponent
12
+
13
+
14
+ class ImportTransformer(ast.NodeTransformer):
15
+ """AST transformer for rewriting imports in component files."""
16
+
17
+ def __init__(
18
+ self,
19
+ original_path: Path,
20
+ target_path: Path,
21
+ import_map: dict[str, str],
22
+ project_root: Path,
23
+ root_file_modules: set[str] | None = None,
24
+ ) -> None:
25
+ """Initialize the import transformer.
26
+
27
+ Args:
28
+ original_path: Path to the original file
29
+ target_path: Path to the target file
30
+ import_map: Mapping of original module paths to generated paths
31
+ project_root: Root path of the project
32
+ root_file_modules: Set of root file module names (without .py extension)
33
+ """
34
+ self.original_path = original_path
35
+ self.target_path = target_path
36
+ self.import_map = import_map
37
+ self.project_root = project_root
38
+ self.root_file_modules = root_file_modules or set()
39
+
40
+ def _calculate_import_depth(self) -> int:
41
+ """Calculate the relative import depth needed to reach build root from component location."""
42
+ try:
43
+ # Get component path relative to project root
44
+ relative_path = self.target_path.relative_to(self.project_root)
45
+
46
+ # Count directory levels: components/tools/weather.py = 2 levels, needs level=2
47
+ # components/tools/api/handler.py = 3 levels, needs level=3
48
+ # Build root contains the root files, so depth = number of path parts
49
+ return len(relative_path.parts) - 1 # Subtract 1 for the filename itself
50
+
51
+ except ValueError:
52
+ # Fallback to level=3 if path calculation fails
53
+ return 3
54
+
55
+ def visit_Import(self, node: ast.Import) -> Any:
56
+ """Transform import statements."""
57
+ new_names = []
58
+
59
+ for alias in node.names:
60
+ module_name = alias.name
61
+
62
+ if module_name in self.root_file_modules:
63
+ # Keep original import unchanged - sys.path will handle resolution
64
+ new_names.append(alias)
65
+ continue
66
+ else:
67
+ new_names.append(alias)
68
+
69
+ # If no root modules, return original or modified import
70
+ if new_names != list(node.names):
71
+ return ast.Import(names=new_names)
72
+ return node
73
+
74
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> Any:
75
+ """Transform import from statements."""
76
+ if node.module is None:
77
+ return node
78
+
79
+ # Check if this is importing from a root file module
80
+ if node.level == 0 and node.module in self.root_file_modules:
81
+ # Keep unchanged - sys.path will handle resolution
82
+ return node
83
+
84
+ # Handle relative imports
85
+ if node.level > 0:
86
+ # Calculate the source module path
87
+ source_dir = self.original_path.parent
88
+ for _ in range(node.level - 1):
89
+ source_dir = source_dir.parent
90
+
91
+ if node.module:
92
+ # Handle imports like `from .helpers import utils`
93
+ source_module = source_dir / node.module.replace(".", "/")
94
+ else:
95
+ # Handle imports like `from . import something`
96
+ source_module = source_dir
97
+
98
+ try:
99
+ # Check if this is a shared module import
100
+ source_str = str(source_module.relative_to(self.project_root))
101
+
102
+ # First, try direct module path match (e.g., "tools/weather/helpers")
103
+ if source_str in self.import_map:
104
+ new_module = self.import_map[source_str]
105
+ return ast.ImportFrom(module=new_module, names=node.names, level=0)
106
+
107
+ # If direct match fails, try directory-based matching
108
+ # This handles cases like `from . import common` where the import_map
109
+ # has "tools/weather/common" but we're looking for "tools/weather"
110
+ source_dir_str = str(source_dir.relative_to(self.project_root))
111
+ if source_dir_str in self.import_map:
112
+ new_module = self.import_map[source_dir_str]
113
+ if node.module:
114
+ new_module = f"{new_module}.{node.module}"
115
+ return ast.ImportFrom(module=new_module, names=node.names, level=0)
116
+
117
+ # Check for specific module imports within the directory
118
+ for import_path, mapped_path in self.import_map.items():
119
+ # Handle cases where we import a specific module from a directory
120
+ # e.g., `from .common import something` should match "tools/weather/common"
121
+ if import_path.startswith(source_dir_str + "/") and node.module:
122
+ module_name = import_path.replace(source_dir_str + "/", "")
123
+ if module_name == node.module:
124
+ return ast.ImportFrom(module=mapped_path, names=node.names, level=0)
125
+
126
+ except ValueError:
127
+ # source_module is not relative to project_root, leave import unchanged
128
+ pass
129
+
130
+ return node
131
+
132
+
133
+ def transform_component(
134
+ component: ParsedComponent | None,
135
+ output_file: Path,
136
+ project_path: Path,
137
+ import_map: dict[str, str],
138
+ source_file: Path | None = None,
139
+ root_file_modules: set[str] | None = None,
140
+ ) -> str:
141
+ """Transform a GolfMCP component into a standalone FastMCP component.
142
+
143
+ Args:
144
+ component: Parsed component to transform (optional if source_file provided)
145
+ output_file: Path to write the transformed component to
146
+ project_path: Path to the project root
147
+ import_map: Mapping of original module paths to generated paths
148
+ source_file: Optional path to source file (for shared files)
149
+ root_file_modules: Set of root file module names (without .py extension)
150
+
151
+ Returns:
152
+ Generated component code
153
+ """
154
+ # Read the original file
155
+ if source_file is not None:
156
+ file_path = source_file
157
+ elif component is not None:
158
+ file_path = Path(component.file_path)
159
+ else:
160
+ raise ValueError("Either component or source_file must be provided")
161
+
162
+ with open(file_path) as f:
163
+ source_code = f.read()
164
+
165
+ # Parse the source code into an AST
166
+ tree = ast.parse(source_code)
167
+
168
+ # Transform imports
169
+ transformer = ImportTransformer(file_path, output_file, import_map, project_path, root_file_modules)
170
+ tree = transformer.visit(tree)
171
+
172
+ # Get all imports and docstring
173
+ imports = []
174
+ docstring = None
175
+
176
+ # Find the module docstring if present
177
+ if (
178
+ len(tree.body) > 0
179
+ and isinstance(tree.body[0], ast.Expr)
180
+ and isinstance(tree.body[0].value, ast.Constant)
181
+ and isinstance(tree.body[0].value.value, str)
182
+ ):
183
+ docstring = tree.body[0].value.value
184
+
185
+ # Find imports
186
+ for node in tree.body:
187
+ if isinstance(node, ast.Import | ast.ImportFrom):
188
+ imports.append(node)
189
+
190
+ # Build full transformed code - start with docstring first (Python convention)
191
+ transformed_code = ""
192
+
193
+ # Add docstring first if present, using proper triple quotes for multi-line docstrings
194
+ if docstring:
195
+ # Check if docstring contains newlines
196
+ if "\n" in docstring:
197
+ # Use triple quotes for multi-line docstrings
198
+ transformed_code += f'"""{docstring}"""\n\n'
199
+ else:
200
+ # Use single quotes for single-line docstrings
201
+ transformed_code += f'"{docstring}"\n\n'
202
+
203
+ # Add transformed imports after docstring
204
+ if imports:
205
+ transformed_imports = ast.unparse(ast.Module(body=imports, type_ignores=[]))
206
+ transformed_code += transformed_imports + "\n\n"
207
+
208
+ # Add the rest of the code except imports and the original docstring
209
+ remaining_nodes = []
210
+ for node in tree.body:
211
+ # Skip imports
212
+ if isinstance(node, ast.Import | ast.ImportFrom):
213
+ continue
214
+
215
+ # Skip the original docstring
216
+ if isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str):
217
+ continue
218
+
219
+ remaining_nodes.append(node)
220
+
221
+ remaining_code = ast.unparse(ast.Module(body=remaining_nodes, type_ignores=[]))
222
+ transformed_code += remaining_code
223
+
224
+ # Ensure the directory exists
225
+ output_file.parent.mkdir(parents=True, exist_ok=True)
226
+
227
+ # Write the transformed code to the output file
228
+ with open(output_file, "w") as f:
229
+ f.write(transformed_code)
230
+
231
+ return transformed_code
File without changes
@@ -0,0 +1,4 @@
1
+ # Golf MCP Server Environment Variables
2
+
3
+ # Development tokens are configured in auth.py for this example
4
+ # Additional environment variables can be added as needed
@@ -0,0 +1,133 @@
1
+ # Golf MCP Project Template (Basic)
2
+
3
+ This is a basic template for creating MCP servers with Golf. It includes development authentication for easy testing. Use `golf init <project-name>` to bootstrap new projects from this template.
4
+
5
+ ## About Golf
6
+
7
+ Golf is a Python framework for building MCP (Model Context Protocol) servers with minimal boilerplate. Define your server's capabilities as simple Python files, and Golf automatically discovers and compiles them into a runnable FastMCP server.
8
+
9
+ ## Getting Started
10
+
11
+ After initializing your project:
12
+
13
+ 1. **Navigate to your project directory:**
14
+ ```bash
15
+ cd your-project-name
16
+ ```
17
+
18
+ 2. **Configure authentication (optional):**
19
+ This template includes development authentication in `auth.py` with sample tokens. Edit the file to set up JWT, OAuth, or API key authentication for production use.
20
+
21
+ 3. **Build and run your server:**
22
+ ```bash
23
+ golf build dev # Development build
24
+ golf run # Start the server
25
+ ```
26
+
27
+ ## Project Structure
28
+
29
+ ```
30
+ your-project/
31
+ ├── tools/ # Tool implementations (functions LLMs can call)
32
+ ├── resources/ # Resource implementations (data LLMs can read)
33
+ ├── prompts/ # Prompt templates (conversation structures)
34
+ ├── golf.json # Server configuration
35
+ └── auth.py # Authentication setup
36
+ ```
37
+
38
+ ## Adding Components
39
+
40
+ ### Tools
41
+ Create `.py` files in `tools/` directory. Each file should export a single async function:
42
+
43
+ ```python
44
+ # tools/calculator.py
45
+ async def add(a: int, b: int) -> int:
46
+ """Add two numbers together."""
47
+ return a + b
48
+
49
+ export = add
50
+ ```
51
+
52
+ ### Resources
53
+ Create `.py` files in `resources/` directory with a `resource_uri` and export function:
54
+
55
+ ```python
56
+ # resources/status.py
57
+ resource_uri = "status://server"
58
+
59
+ async def status() -> dict:
60
+ """Get server status information."""
61
+ return {"status": "running", "timestamp": "2024-01-01T00:00:00Z"}
62
+
63
+ export = status
64
+ ```
65
+
66
+ ### Prompts
67
+ Create `.py` files in `prompts/` directory that return message lists:
68
+
69
+ ```python
70
+ # prompts/assistant.py
71
+ async def assistant() -> list[dict]:
72
+ """System prompt for a helpful assistant."""
73
+ return [
74
+ {
75
+ "role": "system",
76
+ "content": "You are a helpful assistant for {{project_name}}."
77
+ }
78
+ ]
79
+
80
+ export = assistant
81
+ ```
82
+
83
+ ## Authentication Examples
84
+
85
+ ### No Authentication (Default)
86
+ Leave `auth.py` empty or remove it entirely.
87
+
88
+ ### API Key Authentication
89
+ ```python
90
+ # auth.py
91
+ from golf.auth import configure_api_key
92
+
93
+ configure_api_key(
94
+ header_name="Authorization",
95
+ header_prefix="Bearer ",
96
+ required=True
97
+ )
98
+ ```
99
+
100
+ ### JWT Authentication
101
+ ```python
102
+ # auth.py
103
+ from golf.auth import configure_jwt_auth
104
+
105
+ configure_jwt_auth(
106
+ jwks_uri="https://your-domain.auth0.com/.well-known/jwks.json",
107
+ issuer="https://your-domain.auth0.com/",
108
+ audience="https://your-api.example.com"
109
+ )
110
+ ```
111
+
112
+ ### Development Tokens
113
+ ```python
114
+ # auth.py
115
+ from golf.auth import configure_dev_auth
116
+
117
+ configure_dev_auth(
118
+ tokens={
119
+ "dev-token-123": {
120
+ "client_id": "dev-client",
121
+ "scopes": ["read", "write"]
122
+ }
123
+ }
124
+ )
125
+ ```
126
+
127
+ ## Documentation
128
+
129
+ For comprehensive documentation, visit: [https://docs.golf.dev](https://docs.golf.dev)
130
+
131
+ ---
132
+
133
+ Happy building! 🏌️‍♂️
@@ -0,0 +1,76 @@
1
+ """Authentication configuration for the basic Golf MCP server example.
2
+
3
+ This example shows different authentication options available in Golf 0.2.x:
4
+ - JWT authentication with static keys or JWKS endpoints (production)
5
+ - Static token authentication (development/testing)
6
+ - OAuth Server mode (full OAuth 2.0 server)
7
+ - Remote Authorization Server integration
8
+ """
9
+
10
+ # Example 1: JWT authentication with a static public key
11
+ # from golf.auth import configure_auth, JWTAuthConfig
12
+ #
13
+ # configure_auth(
14
+ # JWTAuthConfig(
15
+ # public_key_env_var="JWT_PUBLIC_KEY", # PEM-encoded public key
16
+ # issuer="https://your-auth-server.com",
17
+ # audience="https://your-golf-server.com",
18
+ # required_scopes=["read:data"],
19
+ # )
20
+ # )
21
+
22
+ # Example 2: JWT authentication with JWKS (recommended for production)
23
+ # from golf.auth import configure_auth, JWTAuthConfig
24
+ #
25
+ # configure_auth(
26
+ # JWTAuthConfig(
27
+ # jwks_uri_env_var="JWKS_URI", # e.g., "https://your-domain.auth0.com/.well-known/jwks.json"
28
+ # issuer_env_var="JWT_ISSUER", # e.g., "https://your-domain.auth0.com/"
29
+ # audience_env_var="JWT_AUDIENCE", # e.g., "https://your-api.example.com"
30
+ # required_scopes=["read:user"],
31
+ # )
32
+ # )
33
+
34
+ # Example 3: OAuth Server mode - Golf acts as full OAuth 2.0 authorization server
35
+ # from golf.auth import configure_auth, OAuthServerConfig
36
+ #
37
+ # configure_auth(
38
+ # OAuthServerConfig(
39
+ # base_url_env_var="OAUTH_BASE_URL", # e.g., "https://auth.example.com"
40
+ # valid_scopes=["read", "write", "admin"], # Scopes clients can request
41
+ # default_scopes=["read"], # Default scopes for new clients
42
+ # required_scopes=["read"], # Scopes required for all requests
43
+ # )
44
+ # )
45
+
46
+ # Example 4: Remote Authorization Server integration
47
+ # from golf.auth import configure_auth, RemoteAuthConfig, JWTAuthConfig
48
+ #
49
+ # configure_auth(
50
+ # RemoteAuthConfig(
51
+ # authorization_servers_env_var="AUTH_SERVERS", # Comma-separated: "https://auth1.com,https://auth2.com"
52
+ # resource_server_url_env_var="RESOURCE_URL", # This server's URL
53
+ # token_verifier_config=JWTAuthConfig(
54
+ # jwks_uri_env_var="JWKS_URI"
55
+ # ),
56
+ # )
57
+ # )
58
+
59
+ # Example 5: Static token authentication for development (NOT for production)
60
+ from golf.auth import configure_auth, StaticTokenConfig
61
+
62
+ configure_auth(
63
+ StaticTokenConfig(
64
+ tokens={
65
+ "dev-token-123": {
66
+ "client_id": "dev-client",
67
+ "scopes": ["read", "write"],
68
+ },
69
+ "admin-token-456": {
70
+ "client_id": "admin-client",
71
+ "scopes": ["read", "write", "admin"],
72
+ },
73
+ },
74
+ required_scopes=["read"],
75
+ )
76
+ )
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "basic-server-example",
3
+ "description": "A GolfMCP project",
4
+ "transport": "http"
5
+ }
@@ -0,0 +1,27 @@
1
+ """Welcome prompt for new users."""
2
+
3
+
4
+ async def welcome() -> list[dict]:
5
+ """Provide a welcome prompt for new users.
6
+
7
+ This is a simple example prompt that demonstrates how to define
8
+ a prompt template in GolfMCP.
9
+ """
10
+ return [
11
+ {
12
+ "role": "system",
13
+ "content": (
14
+ "You are an assistant for the {{project_name}} application. "
15
+ "You help users understand how to interact with this system and "
16
+ "its capabilities."
17
+ ),
18
+ },
19
+ {
20
+ "role": "user",
21
+ "content": ("Welcome to {{project_name}}! This is a project built with GolfMCP. How can I get started?"),
22
+ },
23
+ ]
24
+
25
+
26
+ # Designate the entry point function
27
+ export = welcome
@@ -0,0 +1,34 @@
1
+ """Current time resource example."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ # The URI that clients will use to access this resource
7
+ resource_uri = "system://time"
8
+
9
+
10
+ async def current_time() -> dict[str, Any]:
11
+ """Provide the current time in various formats.
12
+
13
+ This is a simple resource example that returns time in all formats.
14
+ """
15
+ now = datetime.now()
16
+
17
+ # Prepare all possible formats
18
+ all_formats = {
19
+ "iso": now.isoformat(),
20
+ "rfc": now.strftime("%a, %d %b %Y %H:%M:%S %z"),
21
+ "unix": int(now.timestamp()),
22
+ "formatted": {
23
+ "date": now.strftime("%Y-%m-%d"),
24
+ "time": now.strftime("%H:%M:%S"),
25
+ "timezone": now.astimezone().tzname(),
26
+ },
27
+ }
28
+
29
+ # Return all formats
30
+ return all_formats
31
+
32
+
33
+ # Designate the entry point function
34
+ export = current_time
@@ -0,0 +1,28 @@
1
+ """Example resource that provides information about the project."""
2
+
3
+ import platform
4
+ from datetime import datetime
5
+ from typing import Any
6
+
7
+ resource_uri = "info://system"
8
+
9
+
10
+ async def info() -> dict[str, Any]:
11
+ """Provide system information as a resource.
12
+
13
+ This is a simple example resource that demonstrates how to expose
14
+ data to an LLM client through the MCP protocol.
15
+ """
16
+ return {
17
+ "project": "{{project_name}}",
18
+ "timestamp": datetime.now().isoformat(),
19
+ "platform": {
20
+ "system": platform.system(),
21
+ "python_version": platform.python_version(),
22
+ "architecture": platform.machine(),
23
+ },
24
+ }
25
+
26
+
27
+ # Designate the entry point function
28
+ export = info
@@ -0,0 +1,46 @@
1
+ """Weather resource template example with URI parameters."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from .client import weather_client
7
+
8
+ # The URI template that clients will use to access this resource
9
+ # The {city} parameter makes this a resource template
10
+ resource_uri = "weather://city/{city}"
11
+
12
+
13
+ async def get_weather_for_city(city: str) -> dict[str, Any]:
14
+ """Provide current weather for a specific city.
15
+
16
+ This example demonstrates:
17
+ 1. Resource templates with URI parameters ({city})
18
+ 2. Dynamic resource access based on parameters
19
+ 3. Using shared client from the client.py file
20
+ 4. FastMCP 2.11+ ResourceTemplate.from_function() usage
21
+
22
+ Args:
23
+ city: The city name to get weather for
24
+
25
+ Returns:
26
+ Weather data for the specified city
27
+ """
28
+ # Use the shared weather client from client.py
29
+ weather_data = await weather_client.get_current(city)
30
+
31
+ # Add some additional data
32
+ weather_data.update(
33
+ {
34
+ "city": city,
35
+ "time": datetime.now().isoformat(),
36
+ "source": "GolfMCP Weather API",
37
+ "unit": "fahrenheit",
38
+ "resource_type": "template",
39
+ }
40
+ )
41
+
42
+ return weather_data
43
+
44
+
45
+ # Designate the entry point function
46
+ export = get_weather_for_city
@@ -0,0 +1,48 @@
1
+ """Weather shared functionality.
2
+
3
+ This file demonstrates the recommended pattern for
4
+ sharing functionality across multiple resources in a directory.
5
+ Golf automatically discovers and includes shared Python files in builds.
6
+ """
7
+
8
+ import os
9
+ from typing import Any
10
+
11
+ # Read configuration from environment variables
12
+ WEATHER_API_KEY = os.environ.get("WEATHER_API_KEY", "mock_key")
13
+ WEATHER_API_URL = os.environ.get("WEATHER_API_URL", "https://api.example.com/weather")
14
+ TEMPERATURE_UNIT = os.environ.get("WEATHER_TEMP_UNIT", "fahrenheit")
15
+
16
+
17
+ class WeatherApiClient:
18
+ """Mock weather API client."""
19
+
20
+ def __init__(self, api_key: str = WEATHER_API_KEY, api_url: str = WEATHER_API_URL) -> None:
21
+ self.api_key = api_key
22
+ self.api_url = api_url
23
+ self.unit = TEMPERATURE_UNIT
24
+
25
+ async def get_forecast(self, city: str, days: int = 3) -> dict[str, Any]:
26
+ """Get weather forecast for a city (mock implementation)."""
27
+ # This would make an API call in a real implementation
28
+ return {
29
+ "city": city,
30
+ "unit": self.unit,
31
+ "forecast": [{"day": i, "temp": 70 + i} for i in range(days)],
32
+ }
33
+
34
+ async def get_current(self, city: str) -> dict[str, Any]:
35
+ """Get current weather for a city (mock implementation)."""
36
+ return {
37
+ "city": city,
38
+ "unit": self.unit,
39
+ "temperature": 72,
40
+ "conditions": "Sunny",
41
+ }
42
+
43
+
44
+ # Create a shared weather client that can be imported by all resources in this directory
45
+ weather_client = WeatherApiClient()
46
+
47
+ # This could also define shared models or other utilities
48
+ # that would be common across weather-related resources
@@ -0,0 +1,36 @@
1
+ """Current weather resource example."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from .client import weather_client
7
+
8
+ # The URI that clients will use to access this resource
9
+ resource_uri = "weather://current"
10
+
11
+
12
+ async def current_weather() -> dict[str, Any]:
13
+ """Provide current weather for a default city.
14
+
15
+ This example demonstrates:
16
+ 1. Nested resource organization (resources/weather/current.py)
17
+ 2. Resource without URI parameters
18
+ 3. Using shared client from the client.py file
19
+ """
20
+ # Use the shared weather client from client.py
21
+ weather_data = await weather_client.get_current("New York")
22
+
23
+ # Add some additional data
24
+ weather_data.update(
25
+ {
26
+ "time": datetime.now().isoformat(),
27
+ "source": "GolfMCP Weather API",
28
+ "unit": "fahrenheit",
29
+ }
30
+ )
31
+
32
+ return weather_data
33
+
34
+
35
+ # Designate the entry point function
36
+ export = current_weather