golf-mcp 0.1.20__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of golf-mcp might be problematic. Click here for more details.
- golf/__init__.py +9 -1
- golf/_endpoints.py +6 -0
- golf/_endpoints_fallback.py +10 -0
- golf/auth/__init__.py +188 -84
- golf/auth/api_key.py +6 -14
- golf/auth/factory.py +333 -0
- golf/auth/helpers.py +12 -42
- golf/auth/providers.py +396 -0
- golf/auth/registry.py +256 -0
- golf/cli/branding.py +192 -0
- golf/cli/main.py +28 -69
- golf/commands/__init__.py +2 -0
- golf/commands/build.py +4 -7
- golf/commands/init.py +30 -53
- golf/commands/run.py +50 -20
- golf/core/builder.py +355 -414
- golf/core/builder_auth.py +63 -144
- golf/core/builder_telemetry.py +26 -3
- golf/core/config.py +38 -59
- golf/core/parser.py +132 -139
- golf/core/platform.py +12 -10
- golf/core/telemetry.py +11 -19
- golf/core/transformer.py +38 -15
- golf/examples/__pycache__/__init__.cpython-311.pyc +0 -0
- golf/examples/basic/.coverage +0 -0
- golf/examples/basic/.env.example +8 -4
- golf/examples/basic/README.md +117 -45
- golf/examples/basic/__pycache__/auth.cpython-311.pyc +0 -0
- golf/examples/basic/auth.py +76 -0
- golf/examples/basic/golf.json +2 -5
- golf/examples/basic/htmlcov/.gitignore +2 -0
- golf/examples/basic/htmlcov/class_index.html +547 -0
- golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +733 -0
- golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
- golf/examples/basic/htmlcov/function_index.html +2091 -0
- golf/examples/basic/htmlcov/index.html +349 -0
- golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
- golf/examples/basic/htmlcov/status.json +1 -0
- golf/examples/basic/htmlcov/style_cb_8e611ae1.css +337 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +323 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +170 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +430 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +288 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +493 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +353 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +120 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +1535 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +289 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +476 -0
- golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +97 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +102 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +178 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +387 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +222 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +106 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +107 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +306 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +329 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +1471 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +186 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +315 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +1149 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +279 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +589 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +286 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +107 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +417 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +150 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +267 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +318 -0
- golf/examples/basic/prompts/__pycache__/welcome.cpython-311.pyc +0 -0
- golf/examples/basic/prompts/welcome.py +3 -5
- golf/examples/basic/resources/__pycache__/current_time.cpython-311.pyc +0 -0
- golf/examples/basic/resources/__pycache__/info.cpython-311.pyc +0 -0
- golf/examples/basic/resources/current_time.py +5 -13
- golf/examples/basic/resources/weather/__pycache__/common.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/current.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/forecast.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/city.py +46 -0
- golf/examples/basic/resources/weather/common.py +4 -11
- golf/examples/basic/resources/weather/current.py +5 -5
- golf/examples/basic/resources/weather/forecast.py +5 -5
- golf/examples/basic/tools/__pycache__/calculator.cpython-311.pyc +0 -0
- golf/examples/basic/tools/calculator.py +94 -0
- golf/examples/basic/tools/say/__pycache__/hello.cpython-311.pyc +0 -0
- golf/examples/basic/tools/say/hello.py +65 -0
- golf/metrics/collector.py +100 -19
- golf/telemetry/__init__.py +4 -0
- golf/telemetry/instrumentation.py +484 -178
- golf/utilities/__init__.py +12 -0
- golf/utilities/context.py +53 -0
- golf/utilities/elicitation.py +170 -0
- golf/utilities/sampling.py +221 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/METADATA +51 -104
- golf_mcp-0.2.0.dist-info/RECORD +110 -0
- golf/auth/oauth.py +0 -861
- golf/auth/provider.py +0 -115
- golf/examples/api_key/.env +0 -2
- golf/examples/api_key/.env.example +0 -1
- golf/examples/api_key/README.md +0 -84
- golf/examples/api_key/golf.json +0 -8
- golf/examples/api_key/pre_build.py +0 -11
- golf/examples/api_key/tools/issues/create.py +0 -93
- golf/examples/api_key/tools/issues/list.py +0 -92
- golf/examples/api_key/tools/repos/list.py +0 -111
- golf/examples/api_key/tools/search/code.py +0 -106
- golf/examples/api_key/tools/users/get.py +0 -82
- golf/examples/basic/.env +0 -5
- golf/examples/basic/pre_build.py +0 -28
- golf/examples/basic/tools/github_user.py +0 -65
- golf/examples/basic/tools/hello.py +0 -34
- golf/examples/basic/tools/payments/charge.py +0 -70
- golf/examples/basic/tools/payments/common.py +0 -36
- golf/examples/basic/tools/payments/refund.py +0 -61
- golf_mcp-0.1.20.dist-info/RECORD +0 -60
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Golf utilities for enhanced MCP tool development.
|
|
2
|
+
|
|
3
|
+
This module provides convenient utilities for Golf tool authors to access
|
|
4
|
+
advanced MCP features like elicitation and sampling without needing to
|
|
5
|
+
manage FastMCP Context objects directly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .elicitation import elicit, elicit_confirmation
|
|
9
|
+
from .sampling import sample, sample_structured, sample_with_context
|
|
10
|
+
from .context import get_current_context
|
|
11
|
+
|
|
12
|
+
__all__ = ["elicit", "elicit_confirmation", "sample", "sample_structured", "sample_with_context", "get_current_context"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Context utilities for Golf MCP tools.
|
|
2
|
+
|
|
3
|
+
This module provides utilities to access the current FastMCP Context
|
|
4
|
+
from within Golf tool functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from fastmcp.server.context import Context
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_current_context() -> "Context":
|
|
14
|
+
"""Get the current FastMCP Context.
|
|
15
|
+
|
|
16
|
+
This function retrieves the current FastMCP Context that was injected
|
|
17
|
+
into the tool function. It works by importing the FastMCP context
|
|
18
|
+
utilities at runtime.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The current FastMCP Context instance
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
RuntimeError: If called outside of an MCP request context
|
|
25
|
+
ImportError: If FastMCP is not available
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
```python
|
|
29
|
+
from golf.utilities import get_current_context
|
|
30
|
+
|
|
31
|
+
async def my_tool(data: str):
|
|
32
|
+
ctx = get_current_context()
|
|
33
|
+
await ctx.info(f"Processing: {data}")
|
|
34
|
+
return "done"
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
# Import FastMCP context utilities at runtime
|
|
39
|
+
from fastmcp.server.context import _current_context
|
|
40
|
+
|
|
41
|
+
# Get the current context from the context variable
|
|
42
|
+
context = _current_context.get(None)
|
|
43
|
+
|
|
44
|
+
if context is None:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
"No FastMCP Context available. This function must be called "
|
|
47
|
+
"from within an MCP tool function that has context injection enabled."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return context
|
|
51
|
+
|
|
52
|
+
except ImportError as e:
|
|
53
|
+
raise ImportError("FastMCP is not available. Please ensure fastmcp>=2.11.0 is installed.") from e
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Elicitation utilities for Golf MCP tools.
|
|
2
|
+
|
|
3
|
+
This module provides simplified elicitation functions that Golf tool authors
|
|
4
|
+
can use without needing to manage FastMCP Context objects directly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, TypeVar, overload
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
|
|
10
|
+
from .context import get_current_context
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
# Apply telemetry instrumentation if available
|
|
15
|
+
try:
|
|
16
|
+
from golf.telemetry import instrument_elicitation
|
|
17
|
+
|
|
18
|
+
_instrumentation_available = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
_instrumentation_available = False
|
|
21
|
+
|
|
22
|
+
def instrument_elicitation(func: Callable, elicitation_type: str = "elicit") -> Callable:
|
|
23
|
+
"""No-op instrumentation when telemetry is not available."""
|
|
24
|
+
return func
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@overload
|
|
28
|
+
async def elicit(
|
|
29
|
+
message: str,
|
|
30
|
+
response_type: None = None,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
"""Elicit with no response type returns empty dict."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@overload
|
|
37
|
+
async def elicit(
|
|
38
|
+
message: str,
|
|
39
|
+
response_type: type[T],
|
|
40
|
+
) -> T:
|
|
41
|
+
"""Elicit with response type returns typed data."""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
async def elicit(
|
|
47
|
+
message: str,
|
|
48
|
+
response_type: list[str],
|
|
49
|
+
) -> str:
|
|
50
|
+
"""Elicit with list of options returns selected string."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def elicit(
|
|
55
|
+
message: str,
|
|
56
|
+
response_type: type[T] | list[str] | None = None,
|
|
57
|
+
) -> T | dict[str, Any] | str:
|
|
58
|
+
"""Request additional information from the user via MCP elicitation.
|
|
59
|
+
|
|
60
|
+
This is a simplified wrapper around FastMCP's Context.elicit() method
|
|
61
|
+
that automatically handles context retrieval and response processing.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
message: Human-readable message explaining what information is needed
|
|
65
|
+
response_type: The type of response expected:
|
|
66
|
+
- None: Returns empty dict (for confirmation prompts)
|
|
67
|
+
- type[T]: Returns validated instance of T (BaseModel, dataclass, etc.)
|
|
68
|
+
- list[str]: Returns selected string from the options
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The user's response in the requested format
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
RuntimeError: If called outside MCP context or user declines/cancels
|
|
75
|
+
ValueError: If response validation fails
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
```python
|
|
79
|
+
from golf.utilities import elicit
|
|
80
|
+
from pydantic import BaseModel
|
|
81
|
+
|
|
82
|
+
class UserInfo(BaseModel):
|
|
83
|
+
name: str
|
|
84
|
+
email: str
|
|
85
|
+
|
|
86
|
+
async def collect_user_info():
|
|
87
|
+
# Structured elicitation
|
|
88
|
+
info = await elicit("Please provide your details:", UserInfo)
|
|
89
|
+
|
|
90
|
+
# Simple text elicitation
|
|
91
|
+
reason = await elicit("Why do you need this?", str)
|
|
92
|
+
|
|
93
|
+
# Multiple choice elicitation
|
|
94
|
+
priority = await elicit("Select priority:", ["low", "medium", "high"])
|
|
95
|
+
|
|
96
|
+
# Confirmation elicitation
|
|
97
|
+
await elicit("Proceed with the action?")
|
|
98
|
+
|
|
99
|
+
return f"User {info.name} requested {reason} with {priority} priority"
|
|
100
|
+
```
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
# Get the current FastMCP context
|
|
104
|
+
ctx = get_current_context()
|
|
105
|
+
|
|
106
|
+
# Call the context's elicit method
|
|
107
|
+
result = await ctx.elicit(message, response_type)
|
|
108
|
+
|
|
109
|
+
# Handle the response based on the action
|
|
110
|
+
if hasattr(result, "action"):
|
|
111
|
+
if result.action == "accept":
|
|
112
|
+
return result.data
|
|
113
|
+
elif result.action == "decline":
|
|
114
|
+
raise RuntimeError(f"User declined the elicitation request: {message}")
|
|
115
|
+
elif result.action == "cancel":
|
|
116
|
+
raise RuntimeError(f"User cancelled the elicitation request: {message}")
|
|
117
|
+
else:
|
|
118
|
+
raise RuntimeError(f"Unexpected elicitation response: {result.action}")
|
|
119
|
+
else:
|
|
120
|
+
# Direct response (shouldn't happen with current FastMCP)
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
if isinstance(e, RuntimeError):
|
|
125
|
+
raise # Re-raise our custom errors
|
|
126
|
+
raise RuntimeError(f"Elicitation failed: {str(e)}") from e
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def elicit_confirmation(message: str) -> bool:
|
|
130
|
+
"""Request a simple yes/no confirmation from the user.
|
|
131
|
+
|
|
132
|
+
This is a convenience function for common confirmation prompts.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
message: The confirmation message to show the user
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
True if user confirmed, False if declined
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
RuntimeError: If user cancels or other error occurs
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
```python
|
|
145
|
+
from golf.utilities import elicit_confirmation
|
|
146
|
+
|
|
147
|
+
async def delete_file(filename: str):
|
|
148
|
+
confirmed = await elicit_confirmation(
|
|
149
|
+
f"Are you sure you want to delete {filename}?"
|
|
150
|
+
)
|
|
151
|
+
if confirmed:
|
|
152
|
+
# Proceed with deletion
|
|
153
|
+
return f"Deleted {filename}"
|
|
154
|
+
else:
|
|
155
|
+
return "Deletion cancelled"
|
|
156
|
+
```
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
# Use elicitation with boolean choice
|
|
160
|
+
choice = await elicit(message, ["yes", "no"])
|
|
161
|
+
return choice.lower() == "yes"
|
|
162
|
+
except RuntimeError as e:
|
|
163
|
+
if "declined" in str(e):
|
|
164
|
+
return False
|
|
165
|
+
raise # Re-raise cancellation or other errors
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Apply instrumentation to all elicitation functions
|
|
169
|
+
elicit = instrument_elicitation(elicit, "elicit")
|
|
170
|
+
elicit_confirmation = instrument_elicitation(elicit_confirmation, "confirmation")
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Sampling utilities for Golf MCP tools.
|
|
2
|
+
|
|
3
|
+
This module provides simplified LLM sampling functions that Golf tool authors
|
|
4
|
+
can use without needing to manage FastMCP Context objects directly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
|
|
10
|
+
from .context import get_current_context
|
|
11
|
+
|
|
12
|
+
# Apply telemetry instrumentation if available
|
|
13
|
+
try:
|
|
14
|
+
from golf.telemetry import instrument_sampling
|
|
15
|
+
|
|
16
|
+
_instrumentation_available = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
_instrumentation_available = False
|
|
19
|
+
|
|
20
|
+
def instrument_sampling(func: Callable, sampling_type: str = "sample") -> Callable:
|
|
21
|
+
"""No-op instrumentation when telemetry is not available."""
|
|
22
|
+
return func
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def sample(
|
|
26
|
+
messages: str | list[str],
|
|
27
|
+
system_prompt: str | None = None,
|
|
28
|
+
temperature: float | None = None,
|
|
29
|
+
max_tokens: int | None = None,
|
|
30
|
+
model_preferences: str | list[str] | None = None,
|
|
31
|
+
) -> str:
|
|
32
|
+
"""Request an LLM completion from the MCP client.
|
|
33
|
+
|
|
34
|
+
This is a simplified wrapper around FastMCP's Context.sample() method
|
|
35
|
+
that automatically handles context retrieval and response processing.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
messages: The message(s) to send to the LLM:
|
|
39
|
+
- str: Single user message
|
|
40
|
+
- list[str]: Multiple user messages
|
|
41
|
+
system_prompt: Optional system prompt to guide the LLM
|
|
42
|
+
temperature: Optional temperature for sampling (0.0 to 1.0)
|
|
43
|
+
max_tokens: Optional maximum tokens to generate (default: 512)
|
|
44
|
+
model_preferences: Optional model preferences:
|
|
45
|
+
- str: Single model name hint
|
|
46
|
+
- list[str]: Multiple model name hints in preference order
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The LLM's response as a string
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
RuntimeError: If called outside MCP context or sampling fails
|
|
53
|
+
ValueError: If parameters are invalid
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
```python
|
|
57
|
+
from golf.utilities import sample
|
|
58
|
+
|
|
59
|
+
async def analyze_data(data: str):
|
|
60
|
+
# Simple completion
|
|
61
|
+
analysis = await sample(f"Analyze this data: {data}")
|
|
62
|
+
|
|
63
|
+
# With system prompt and temperature
|
|
64
|
+
creative_response = await sample(
|
|
65
|
+
"Write a creative story about this data",
|
|
66
|
+
system_prompt="You are a creative writer",
|
|
67
|
+
temperature=0.8,
|
|
68
|
+
max_tokens=1000
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# With model preferences
|
|
72
|
+
technical_analysis = await sample(
|
|
73
|
+
f"Provide technical analysis: {data}",
|
|
74
|
+
model_preferences=["gpt-4", "claude-3-sonnet"]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"analysis": analysis,
|
|
79
|
+
"creative": creative_response,
|
|
80
|
+
"technical": technical_analysis
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
# Get the current FastMCP context
|
|
86
|
+
ctx = get_current_context()
|
|
87
|
+
|
|
88
|
+
# Call the context's sample method
|
|
89
|
+
result = await ctx.sample(
|
|
90
|
+
messages=messages,
|
|
91
|
+
system_prompt=system_prompt,
|
|
92
|
+
temperature=temperature,
|
|
93
|
+
max_tokens=max_tokens,
|
|
94
|
+
model_preferences=model_preferences,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Extract text content from the ContentBlock response
|
|
98
|
+
if hasattr(result, "text"):
|
|
99
|
+
return result.text
|
|
100
|
+
elif hasattr(result, "content"):
|
|
101
|
+
# Handle different content block types
|
|
102
|
+
if isinstance(result.content, str):
|
|
103
|
+
return result.content
|
|
104
|
+
elif hasattr(result.content, "text"):
|
|
105
|
+
return result.content.text
|
|
106
|
+
else:
|
|
107
|
+
return str(result.content)
|
|
108
|
+
else:
|
|
109
|
+
return str(result)
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
raise RuntimeError(f"LLM sampling failed: {str(e)}") from e
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def sample_structured(
|
|
116
|
+
messages: str | list[str],
|
|
117
|
+
format_instructions: str,
|
|
118
|
+
system_prompt: str | None = None,
|
|
119
|
+
temperature: float = 0.1,
|
|
120
|
+
max_tokens: int | None = None,
|
|
121
|
+
) -> str:
|
|
122
|
+
"""Request a structured LLM completion with specific formatting.
|
|
123
|
+
|
|
124
|
+
This is a convenience function for requesting structured responses
|
|
125
|
+
like JSON, XML, or other formatted output.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
messages: The message(s) to send to the LLM
|
|
129
|
+
format_instructions: Instructions for the desired output format
|
|
130
|
+
system_prompt: Optional system prompt
|
|
131
|
+
temperature: Temperature for sampling (default: 0.1 for consistency)
|
|
132
|
+
max_tokens: Optional maximum tokens to generate
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The structured LLM response as a string
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
```python
|
|
139
|
+
from golf.utilities import sample_structured
|
|
140
|
+
|
|
141
|
+
async def extract_entities(text: str):
|
|
142
|
+
entities = await sample_structured(
|
|
143
|
+
f"Extract entities from: {text}",
|
|
144
|
+
format_instructions="Return as JSON with keys: persons, "
|
|
145
|
+
"organizations, locations",
|
|
146
|
+
system_prompt="You are an expert at named entity recognition"
|
|
147
|
+
)
|
|
148
|
+
return entities
|
|
149
|
+
```
|
|
150
|
+
"""
|
|
151
|
+
# Combine the format instructions with the messages
|
|
152
|
+
if isinstance(messages, str):
|
|
153
|
+
formatted_message = f"{messages}\n\n{format_instructions}"
|
|
154
|
+
else:
|
|
155
|
+
formatted_message = messages + [format_instructions]
|
|
156
|
+
|
|
157
|
+
return await sample(
|
|
158
|
+
messages=formatted_message,
|
|
159
|
+
system_prompt=system_prompt,
|
|
160
|
+
temperature=temperature,
|
|
161
|
+
max_tokens=max_tokens,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def sample_with_context(
|
|
166
|
+
messages: str | list[str],
|
|
167
|
+
context_data: dict[str, Any],
|
|
168
|
+
system_prompt: str | None = None,
|
|
169
|
+
**kwargs: Any,
|
|
170
|
+
) -> str:
|
|
171
|
+
"""Request an LLM completion with additional context data.
|
|
172
|
+
|
|
173
|
+
This convenience function formats context data and includes it
|
|
174
|
+
in the sampling request.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
messages: The message(s) to send to the LLM
|
|
178
|
+
context_data: Dictionary of context data to include
|
|
179
|
+
system_prompt: Optional system prompt
|
|
180
|
+
**kwargs: Additional arguments passed to sample()
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
The LLM response as a string
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
```python
|
|
187
|
+
from golf.utilities import sample_with_context
|
|
188
|
+
|
|
189
|
+
async def generate_report(topic: str, user_data: dict):
|
|
190
|
+
report = await sample_with_context(
|
|
191
|
+
f"Generate a report about {topic}",
|
|
192
|
+
context_data={
|
|
193
|
+
"user_preferences": user_data,
|
|
194
|
+
"timestamp": "2024-01-01",
|
|
195
|
+
"format": "markdown"
|
|
196
|
+
},
|
|
197
|
+
system_prompt="You are a professional report writer"
|
|
198
|
+
)
|
|
199
|
+
return report
|
|
200
|
+
```
|
|
201
|
+
"""
|
|
202
|
+
# Format context data as a readable string
|
|
203
|
+
context_str = "\n".join([f"{k}: {v}" for k, v in context_data.items()])
|
|
204
|
+
|
|
205
|
+
# Add context to the message
|
|
206
|
+
if isinstance(messages, str):
|
|
207
|
+
contextual_message = f"{messages}\n\nContext:\n{context_str}"
|
|
208
|
+
else:
|
|
209
|
+
contextual_message = messages + [f"Context:\n{context_str}"]
|
|
210
|
+
|
|
211
|
+
return await sample(
|
|
212
|
+
messages=contextual_message,
|
|
213
|
+
system_prompt=system_prompt,
|
|
214
|
+
**kwargs,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# Apply instrumentation to all sampling functions
|
|
219
|
+
sample = instrument_sampling(sample, "sample")
|
|
220
|
+
sample_structured = instrument_sampling(sample_structured, "structured")
|
|
221
|
+
sample_with_context = instrument_sampling(sample_with_context, "context")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: golf-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Framework for building MCP servers
|
|
5
5
|
Author-email: Antoni Gmitruk <antoni@golf.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -21,8 +21,9 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: typer>=0.15.4
|
|
23
23
|
Requires-Dist: rich>=14.0.0
|
|
24
|
-
Requires-Dist: fastmcp<
|
|
24
|
+
Requires-Dist: fastmcp<3.0.0,>=2.11.0
|
|
25
25
|
Requires-Dist: pydantic>=2.11.0
|
|
26
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
26
27
|
Requires-Dist: python-dotenv>=1.1.0
|
|
27
28
|
Requires-Dist: black>=24.10.0
|
|
28
29
|
Requires-Dist: pyjwt>=2.0.0
|
|
@@ -67,9 +68,9 @@ Dynamic: license-file
|
|
|
67
68
|
|
|
68
69
|
## Overview
|
|
69
70
|
|
|
70
|
-
Golf is a **framework** designed to streamline the creation of MCP server applications. It allows developers to define server's capabilities—*tools*, *prompts*, and *resources*—as simple Python files within a conventional directory structure. Golf then automatically discovers, parses, and compiles these components into a runnable
|
|
71
|
+
Golf is a **framework** designed to streamline the creation of MCP server applications. It allows developers to define server's capabilities—*tools*, *prompts*, and *resources*—as simple Python files within a conventional directory structure. Golf then automatically discovers, parses, and compiles these components into a runnable MCP server, minimizing boilerplate and accelerating development.
|
|
71
72
|
|
|
72
|
-
With Golf, you
|
|
73
|
+
With Golf v0.2.0, you get **enterprise-grade authentication** (JWT, OAuth Server, API key, development tokens), **built-in utilities** for LLM interactions, and **automatic telemetry** integration. Focus on implementing your agent's logic while Golf handles authentication, monitoring, and server infrastructure.
|
|
73
74
|
|
|
74
75
|
## Quick Start
|
|
75
76
|
|
|
@@ -101,7 +102,7 @@ cd your-project-name
|
|
|
101
102
|
golf build dev
|
|
102
103
|
golf run
|
|
103
104
|
```
|
|
104
|
-
This will start the
|
|
105
|
+
This will start the MCP server, typically on `http://localhost:3000` (configurable in `golf.json`).
|
|
105
106
|
|
|
106
107
|
That's it! Your Golf server is running and ready for integration.
|
|
107
108
|
|
|
@@ -124,10 +125,11 @@ A Golf project initialized with `golf init` will have a structure similar to thi
|
|
|
124
125
|
│ └─ welcome.py # Example prompt
|
|
125
126
|
│
|
|
126
127
|
├─ .env # Environment variables (e.g., API keys, server port)
|
|
127
|
-
└─
|
|
128
|
+
└─ auth.py # Authentication configuration (JWT, OAuth Server, API key, dev tokens)
|
|
128
129
|
```
|
|
129
130
|
|
|
130
131
|
- **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
|
|
132
|
+
- **`auth.py`**: Dedicated authentication configuration file (new in v0.2.0, breaking change from v0.1.x authentication API) for JWT, OAuth Server, API key, or development authentication.
|
|
131
133
|
- **`tools/`**, **`resources/`**, **`prompts/`**: Contain your Python files, each defining a single component. These directories can also contain nested subdirectories to further organize your components (e.g., `tools/payments/charge.py`). The module docstring of each file serves as the component's description.
|
|
132
134
|
- Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit_payments` (filename, followed by reversed parent directories under the main category, joined by underscores).
|
|
133
135
|
- **`common.py`** (not shown, but can be placed in subdirectories like `tools/payments/common.py`): Used to share code (clients, models, etc.) among components in the same subdirectory.
|
|
@@ -164,118 +166,63 @@ export = hello
|
|
|
164
166
|
```
|
|
165
167
|
Golf will automatically discover this file. The module docstring `"""Hello World tool {{project_name}}."""` is used as the tool's description. It infers parameters from the `hello` function's signature and uses the `Output` Pydantic model for the output schema. The tool will be registered with the ID `hello`.
|
|
166
168
|
|
|
167
|
-
##
|
|
169
|
+
## Authentication & Features
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
Golf includes enterprise-grade authentication, built-in utilities, and automatic telemetry:
|
|
170
172
|
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Key Configuration Options:
|
|
199
|
-
|
|
200
|
-
- **`name`**: The identifier for your MCP server. This will be shown to clients connecting to your server.
|
|
201
|
-
- **`transport`**: Choose based on your client needs:
|
|
202
|
-
- `"sse"` is ideal for web-based clients and real-time communication
|
|
203
|
-
- `"streamable-http"` provides HTTP streaming for traditional API clients
|
|
204
|
-
- `"stdio"` enables integration with command-line tools and scripts
|
|
205
|
-
- **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
|
|
206
|
-
- **`stateless_http`**: When true, makes the streamable-http transport stateless by creating a new session for each request. This ensures that server restarts don't break existing client connections, making the server truly stateless.
|
|
207
|
-
- **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
|
|
208
|
-
- **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
|
|
209
|
-
- **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
|
|
210
|
-
- **`opentelemetry_enabled`**: When true, enables distributed tracing for debugging and monitoring your MCP server
|
|
211
|
-
- **`opentelemetry_default_exporter`**: Sets the default trace exporter. Can be overridden by the `OTEL_TRACES_EXPORTER` environment variable
|
|
212
|
-
|
|
213
|
-
## Features
|
|
214
|
-
|
|
215
|
-
### 🏥 Health Check Support
|
|
216
|
-
|
|
217
|
-
Golf includes built-in health check endpoint support for production deployments. When enabled, it automatically adds a custom HTTP route that can be used by:
|
|
218
|
-
- Kubernetes readiness and liveness probes
|
|
219
|
-
- Load balancers and reverse proxies
|
|
220
|
-
- Monitoring systems
|
|
221
|
-
- Container orchestration platforms
|
|
222
|
-
|
|
223
|
-
#### Configuration
|
|
224
|
-
|
|
225
|
-
Enable health checks in your `golf.json`:
|
|
226
|
-
```json
|
|
227
|
-
{
|
|
228
|
-
"health_check_enabled": true,
|
|
229
|
-
"health_check_path": "/health",
|
|
230
|
-
"health_check_response": "Service is healthy"
|
|
231
|
-
}
|
|
173
|
+
```python
|
|
174
|
+
# auth.py - Configure authentication
|
|
175
|
+
from golf.auth import configure_auth, JWTAuthConfig, StaticTokenConfig, OAuthServerConfig
|
|
176
|
+
|
|
177
|
+
# JWT authentication (production)
|
|
178
|
+
configure_auth(JWTAuthConfig(
|
|
179
|
+
jwks_uri_env_var="JWKS_URI",
|
|
180
|
+
issuer_env_var="JWT_ISSUER",
|
|
181
|
+
audience_env_var="JWT_AUDIENCE",
|
|
182
|
+
required_scopes=["read", "write"]
|
|
183
|
+
))
|
|
184
|
+
|
|
185
|
+
# OAuth Server mode (Golf acts as OAuth 2.0 server)
|
|
186
|
+
# configure_auth(OAuthServerConfig(
|
|
187
|
+
# base_url="https://your-golf-server.com",
|
|
188
|
+
# valid_scopes=["read", "write", "admin"]
|
|
189
|
+
# ))
|
|
190
|
+
|
|
191
|
+
# Static tokens (development only)
|
|
192
|
+
# configure_auth(StaticTokenConfig(
|
|
193
|
+
# tokens={"dev-token": {"client_id": "dev", "scopes": ["read"]}}
|
|
194
|
+
# ))
|
|
195
|
+
|
|
196
|
+
# Built-in utilities available in all tools
|
|
197
|
+
from golf.utils import elicit, sample, get_context
|
|
232
198
|
```
|
|
233
199
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"""Health check endpoint for Kubernetes and load balancers."""
|
|
239
|
-
return PlainTextResponse("Service is healthy")
|
|
200
|
+
```bash
|
|
201
|
+
# Automatic telemetry with Golf Platform
|
|
202
|
+
export GOLF_API_KEY="your-key"
|
|
203
|
+
golf run # ✅ Telemetry enabled automatically
|
|
240
204
|
```
|
|
241
205
|
|
|
242
|
-
|
|
206
|
+
**[📚 Complete Documentation →](https://docs.golf.dev)**
|
|
243
207
|
|
|
244
|
-
|
|
245
|
-
- Tool executions with arguments and results
|
|
246
|
-
- Resource reads and template expansions
|
|
247
|
-
- Prompt generations
|
|
248
|
-
- HTTP requests and sessions
|
|
208
|
+
## Configuration
|
|
249
209
|
|
|
250
|
-
|
|
210
|
+
Basic configuration in `golf.json`:
|
|
251
211
|
|
|
252
|
-
Enable OpenTelemetry in your `golf.json`:
|
|
253
212
|
```json
|
|
254
213
|
{
|
|
255
|
-
"
|
|
256
|
-
"
|
|
214
|
+
"name": "My Golf Server",
|
|
215
|
+
"host": "localhost",
|
|
216
|
+
"port": 3000,
|
|
217
|
+
"transport": "sse",
|
|
218
|
+
"opentelemetry_enabled": false,
|
|
219
|
+
"detailed_tracing": false
|
|
257
220
|
}
|
|
258
221
|
```
|
|
259
222
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
OTEL_TRACES_EXPORTER=otlp_http
|
|
264
|
-
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
|
|
265
|
-
OTEL_SERVICE_NAME=my-golf-server # Optional, defaults to project name
|
|
266
|
-
|
|
267
|
-
# For console exporter (debugging)
|
|
268
|
-
OTEL_TRACES_EXPORTER=console
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
**Note**: When using the OTLP HTTP exporter, you must set `OTEL_EXPORTER_OTLP_ENDPOINT`. If not configured, Golf will display a warning and disable tracing to avoid errors.
|
|
272
|
-
|
|
273
|
-
## Roadmap
|
|
274
|
-
|
|
275
|
-
Here are the things we are working hard on:
|
|
276
|
-
|
|
277
|
-
* **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
|
|
278
|
-
* **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
|
|
223
|
+
- **`transport`**: Choose `"sse"`, `"streamable-http"`, or `"stdio"`
|
|
224
|
+
- **`opentelemetry_enabled`**: Auto-enabled with `GOLF_API_KEY`
|
|
225
|
+
- **`detailed_tracing`**: Capture input/output (use carefully with sensitive data)
|
|
279
226
|
|
|
280
227
|
|
|
281
228
|
## Privacy & Telemetry
|