fastmcp 2.12.1__py3-none-any.whl → 2.12.3__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.
- fastmcp/cli/claude.py +1 -10
- fastmcp/cli/cli.py +45 -25
- fastmcp/cli/install/__init__.py +2 -0
- fastmcp/cli/install/claude_code.py +1 -10
- fastmcp/cli/install/claude_desktop.py +1 -9
- fastmcp/cli/install/cursor.py +2 -18
- fastmcp/cli/install/gemini_cli.py +242 -0
- fastmcp/cli/install/mcp_json.py +1 -9
- fastmcp/cli/run.py +0 -84
- fastmcp/client/auth/oauth.py +1 -1
- fastmcp/client/client.py +6 -6
- fastmcp/client/elicitation.py +6 -1
- fastmcp/client/transports.py +1 -1
- fastmcp/contrib/component_manager/component_service.py +1 -1
- fastmcp/contrib/mcp_mixin/README.md +1 -1
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +41 -6
- fastmcp/experimental/utilities/openapi/director.py +8 -1
- fastmcp/prompts/prompt.py +10 -8
- fastmcp/resources/resource.py +14 -11
- fastmcp/resources/template.py +12 -10
- fastmcp/server/auth/auth.py +7 -1
- fastmcp/server/auth/oauth_proxy.py +51 -11
- fastmcp/server/context.py +10 -10
- fastmcp/server/dependencies.py +18 -5
- fastmcp/server/server.py +7 -5
- fastmcp/settings.py +15 -1
- fastmcp/tools/tool.py +101 -85
- fastmcp/tools/tool_transform.py +1 -1
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +4 -39
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +2 -2
- fastmcp/utilities/mcp_server_config/v1/schema.json +2 -1
- fastmcp/utilities/types.py +9 -5
- {fastmcp-2.12.1.dist-info → fastmcp-2.12.3.dist-info}/METADATA +2 -2
- {fastmcp-2.12.1.dist-info → fastmcp-2.12.3.dist-info}/RECORD +37 -36
- {fastmcp-2.12.1.dist-info → fastmcp-2.12.3.dist-info}/WHEEL +0 -0
- {fastmcp-2.12.1.dist-info → fastmcp-2.12.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.1.dist-info → fastmcp-2.12.3.dist-info}/licenses/LICENSE +0 -0
fastmcp/client/transports.py
CHANGED
|
@@ -609,7 +609,7 @@ class UvStdioTransport(StdioTransport):
|
|
|
609
609
|
uv_args: list[str] = []
|
|
610
610
|
|
|
611
611
|
# Check if we need any environment setup
|
|
612
|
-
if env_config.
|
|
612
|
+
if env_config._must_run_with_uv():
|
|
613
613
|
# Use the config to build args, but we need to handle the command differently
|
|
614
614
|
# since transport has specific needs
|
|
615
615
|
uv_args = ["run"]
|
|
@@ -16,7 +16,7 @@ Prompts:
|
|
|
16
16
|
* [enable/disable](https://gofastmcp.com/servers/prompts#disabling-prompts)
|
|
17
17
|
|
|
18
18
|
Resources:
|
|
19
|
-
* [enable/
|
|
19
|
+
* [enable/disable](https://gofastmcp.com/servers/resources#disabling-resources)
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
@@ -8,6 +8,7 @@ from mcp.types import ToolAnnotations
|
|
|
8
8
|
from fastmcp.prompts.prompt import Prompt
|
|
9
9
|
from fastmcp.resources.resource import Resource
|
|
10
10
|
from fastmcp.tools.tool import Tool
|
|
11
|
+
from fastmcp.utilities.types import get_fn_name
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from fastmcp.server import FastMCP
|
|
@@ -34,7 +35,7 @@ def mcp_tool(
|
|
|
34
35
|
|
|
35
36
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
36
37
|
call_args = {
|
|
37
|
-
"name": name or func
|
|
38
|
+
"name": name or get_fn_name(func),
|
|
38
39
|
"description": description,
|
|
39
40
|
"tags": tags,
|
|
40
41
|
"annotations": annotations,
|
|
@@ -63,7 +64,7 @@ def mcp_resource(
|
|
|
63
64
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
64
65
|
call_args = {
|
|
65
66
|
"uri": uri,
|
|
66
|
-
"name": name or func
|
|
67
|
+
"name": name or get_fn_name(func),
|
|
67
68
|
"description": description,
|
|
68
69
|
"mime_type": mime_type,
|
|
69
70
|
"tags": tags,
|
|
@@ -88,7 +89,7 @@ def mcp_prompt(
|
|
|
88
89
|
|
|
89
90
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
90
91
|
call_args = {
|
|
91
|
-
"name": name or func
|
|
92
|
+
"name": name or get_fn_name(func),
|
|
92
93
|
"description": description,
|
|
93
94
|
"tags": tags,
|
|
94
95
|
"enabled": enabled,
|
|
@@ -146,7 +147,21 @@ class MCPMixin:
|
|
|
146
147
|
registration_info["name"] = (
|
|
147
148
|
f"{prefix}{separator}{registration_info['name']}"
|
|
148
149
|
)
|
|
149
|
-
|
|
150
|
+
|
|
151
|
+
tool = Tool.from_function(
|
|
152
|
+
fn=method,
|
|
153
|
+
name=registration_info.get("name"),
|
|
154
|
+
title=registration_info.get("title"),
|
|
155
|
+
description=registration_info.get("description"),
|
|
156
|
+
tags=registration_info.get("tags"),
|
|
157
|
+
annotations=registration_info.get("annotations"),
|
|
158
|
+
exclude_args=registration_info.get("exclude_args"),
|
|
159
|
+
serializer=registration_info.get("serializer"),
|
|
160
|
+
output_schema=registration_info.get("output_schema"),
|
|
161
|
+
meta=registration_info.get("meta"),
|
|
162
|
+
enabled=registration_info.get("enabled"),
|
|
163
|
+
)
|
|
164
|
+
|
|
150
165
|
mcp_server.add_tool(tool)
|
|
151
166
|
|
|
152
167
|
def register_resources(
|
|
@@ -175,7 +190,19 @@ class MCPMixin:
|
|
|
175
190
|
registration_info["uri"] = (
|
|
176
191
|
f"{prefix}{separator}{registration_info['uri']}"
|
|
177
192
|
)
|
|
178
|
-
|
|
193
|
+
|
|
194
|
+
resource = Resource.from_function(
|
|
195
|
+
fn=method,
|
|
196
|
+
uri=registration_info["uri"],
|
|
197
|
+
name=registration_info.get("name"),
|
|
198
|
+
description=registration_info.get("description"),
|
|
199
|
+
mime_type=registration_info.get("mime_type"),
|
|
200
|
+
tags=registration_info.get("tags"),
|
|
201
|
+
enabled=registration_info.get("enabled"),
|
|
202
|
+
annotations=registration_info.get("annotations"),
|
|
203
|
+
meta=registration_info.get("meta"),
|
|
204
|
+
)
|
|
205
|
+
|
|
179
206
|
mcp_server.add_resource(resource)
|
|
180
207
|
|
|
181
208
|
def register_prompts(
|
|
@@ -200,7 +227,15 @@ class MCPMixin:
|
|
|
200
227
|
registration_info["name"] = (
|
|
201
228
|
f"{prefix}{separator}{registration_info['name']}"
|
|
202
229
|
)
|
|
203
|
-
prompt = Prompt.from_function(
|
|
230
|
+
prompt = Prompt.from_function(
|
|
231
|
+
fn=method,
|
|
232
|
+
name=registration_info.get("name"),
|
|
233
|
+
title=registration_info.get("title"),
|
|
234
|
+
description=registration_info.get("description"),
|
|
235
|
+
tags=registration_info.get("tags"),
|
|
236
|
+
enabled=registration_info.get("enabled"),
|
|
237
|
+
meta=registration_info.get("meta"),
|
|
238
|
+
)
|
|
204
239
|
mcp_server.add_prompt(prompt)
|
|
205
240
|
|
|
206
241
|
def register_all(
|
|
@@ -69,7 +69,14 @@ class RequestDirector:
|
|
|
69
69
|
request_data["content"] = body
|
|
70
70
|
|
|
71
71
|
# Step 5: Create httpx.Request
|
|
72
|
-
return httpx.Request(
|
|
72
|
+
return httpx.Request(
|
|
73
|
+
method=request_data["method"],
|
|
74
|
+
url=request_data["url"],
|
|
75
|
+
params=request_data.get("params"),
|
|
76
|
+
headers=request_data.get("headers"),
|
|
77
|
+
json=request_data.get("json"),
|
|
78
|
+
content=request_data.get("content"),
|
|
79
|
+
)
|
|
73
80
|
|
|
74
81
|
def _unflatten_arguments(
|
|
75
82
|
self, route: HTTPRoute, flat_args: dict[str, Any]
|
fastmcp/prompts/prompt.py
CHANGED
|
@@ -100,14 +100,16 @@ class Prompt(FastMCPComponent, ABC):
|
|
|
100
100
|
)
|
|
101
101
|
for arg in self.arguments or []
|
|
102
102
|
]
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
|
|
104
|
+
return MCPPrompt(
|
|
105
|
+
name=overrides.get("name", self.name),
|
|
106
|
+
description=overrides.get("description", self.description),
|
|
107
|
+
arguments=arguments,
|
|
108
|
+
title=overrides.get("title", self.title),
|
|
109
|
+
_meta=overrides.get(
|
|
110
|
+
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
|
111
|
+
),
|
|
112
|
+
)
|
|
111
113
|
|
|
112
114
|
@staticmethod
|
|
113
115
|
def from_function(
|
fastmcp/resources/resource.py
CHANGED
|
@@ -24,6 +24,7 @@ from fastmcp.server.dependencies import get_context
|
|
|
24
24
|
from fastmcp.utilities.components import FastMCPComponent
|
|
25
25
|
from fastmcp.utilities.types import (
|
|
26
26
|
find_kwarg_by_type,
|
|
27
|
+
get_fn_name,
|
|
27
28
|
)
|
|
28
29
|
|
|
29
30
|
if TYPE_CHECKING:
|
|
@@ -122,16 +123,18 @@ class Resource(FastMCPComponent, abc.ABC):
|
|
|
122
123
|
**overrides: Any,
|
|
123
124
|
) -> MCPResource:
|
|
124
125
|
"""Convert the resource to an MCPResource."""
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
"name"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
|
|
127
|
+
return MCPResource(
|
|
128
|
+
name=overrides.get("name", self.name),
|
|
129
|
+
uri=overrides.get("uri", self.uri),
|
|
130
|
+
description=overrides.get("description", self.description),
|
|
131
|
+
mimeType=overrides.get("mimeType", self.mime_type),
|
|
132
|
+
title=overrides.get("title", self.title),
|
|
133
|
+
annotations=overrides.get("annotations", self.annotations),
|
|
134
|
+
_meta=overrides.get(
|
|
135
|
+
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
|
136
|
+
),
|
|
137
|
+
)
|
|
135
138
|
|
|
136
139
|
def __repr__(self) -> str:
|
|
137
140
|
return f"{self.__class__.__name__}(uri={self.uri!r}, name={self.name!r}, description={self.description!r}, tags={self.tags})"
|
|
@@ -182,7 +185,7 @@ class FunctionResource(Resource):
|
|
|
182
185
|
return cls(
|
|
183
186
|
fn=fn,
|
|
184
187
|
uri=uri,
|
|
185
|
-
name=name or fn
|
|
188
|
+
name=name or get_fn_name(fn),
|
|
186
189
|
title=title,
|
|
187
190
|
description=description or inspect.getdoc(fn),
|
|
188
191
|
mime_type=mime_type or "text/plain",
|
fastmcp/resources/template.py
CHANGED
|
@@ -154,16 +154,18 @@ class ResourceTemplate(FastMCPComponent):
|
|
|
154
154
|
**overrides: Any,
|
|
155
155
|
) -> MCPResourceTemplate:
|
|
156
156
|
"""Convert the resource template to an MCPResourceTemplate."""
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
"name"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
|
|
157
|
+
|
|
158
|
+
return MCPResourceTemplate(
|
|
159
|
+
name=overrides.get("name", self.name),
|
|
160
|
+
uriTemplate=overrides.get("uriTemplate", self.uri_template),
|
|
161
|
+
description=overrides.get("description", self.description),
|
|
162
|
+
mimeType=overrides.get("mimeType", self.mime_type),
|
|
163
|
+
title=overrides.get("title", self.title),
|
|
164
|
+
annotations=overrides.get("annotations", self.annotations),
|
|
165
|
+
_meta=overrides.get(
|
|
166
|
+
"_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
|
|
167
|
+
),
|
|
168
|
+
)
|
|
167
169
|
|
|
168
170
|
@classmethod
|
|
169
171
|
def from_mcp_template(cls, mcp_template: MCPResourceTemplate) -> ResourceTemplate:
|
fastmcp/server/auth/auth.py
CHANGED
|
@@ -353,10 +353,16 @@ class OAuthProvider(
|
|
|
353
353
|
|
|
354
354
|
# Add protected resource routes if this server is also acting as a resource server
|
|
355
355
|
if resource_url:
|
|
356
|
+
supported_scopes = (
|
|
357
|
+
self.client_registration_options.valid_scopes
|
|
358
|
+
if self.client_registration_options
|
|
359
|
+
and self.client_registration_options.valid_scopes
|
|
360
|
+
else self.required_scopes
|
|
361
|
+
)
|
|
356
362
|
protected_routes = create_protected_resource_routes(
|
|
357
363
|
resource_url=resource_url,
|
|
358
364
|
authorization_servers=[self.issuer_url],
|
|
359
|
-
scopes_supported=
|
|
365
|
+
scopes_supported=supported_scopes,
|
|
360
366
|
)
|
|
361
367
|
oauth_routes.extend(protected_routes)
|
|
362
368
|
|
|
@@ -250,6 +250,10 @@ class OAuthProxy(OAuthProvider):
|
|
|
250
250
|
forward_pkce: bool = True,
|
|
251
251
|
# Token endpoint authentication
|
|
252
252
|
token_endpoint_auth_method: str | None = None,
|
|
253
|
+
# Extra parameters to forward to authorization endpoint
|
|
254
|
+
extra_authorize_params: dict[str, str] | None = None,
|
|
255
|
+
# Extra parameters to forward to token endpoint
|
|
256
|
+
extra_token_params: dict[str, str] | None = None,
|
|
253
257
|
):
|
|
254
258
|
"""Initialize the OAuth proxy provider.
|
|
255
259
|
|
|
@@ -278,6 +282,11 @@ class OAuthProxy(OAuthProvider):
|
|
|
278
282
|
token_endpoint_auth_method: Token endpoint authentication method for upstream server.
|
|
279
283
|
Common values: "client_secret_basic", "client_secret_post", "none".
|
|
280
284
|
If None, authlib will use its default (typically "client_secret_basic").
|
|
285
|
+
extra_authorize_params: Additional parameters to forward to the upstream authorization endpoint.
|
|
286
|
+
Useful for provider-specific parameters like Auth0's "audience".
|
|
287
|
+
Example: {"audience": "https://api.example.com"}
|
|
288
|
+
extra_token_params: Additional parameters to forward to the upstream token endpoint.
|
|
289
|
+
Useful for provider-specific parameters during token exchange.
|
|
281
290
|
"""
|
|
282
291
|
# Always enable DCR since we implement it locally for MCP clients
|
|
283
292
|
client_registration_options = ClientRegistrationOptions(
|
|
@@ -319,6 +328,10 @@ class OAuthProxy(OAuthProvider):
|
|
|
319
328
|
# Token endpoint authentication
|
|
320
329
|
self._token_endpoint_auth_method = token_endpoint_auth_method
|
|
321
330
|
|
|
331
|
+
# Extra parameters for authorization and token endpoints
|
|
332
|
+
self._extra_authorize_params = extra_authorize_params or {}
|
|
333
|
+
self._extra_token_params = extra_token_params or {}
|
|
334
|
+
|
|
322
335
|
# Local state for DCR and token bookkeeping
|
|
323
336
|
self._clients: dict[str, OAuthClientInformationFull] = {}
|
|
324
337
|
self._access_tokens: dict[str, AccessToken] = {}
|
|
@@ -485,6 +498,24 @@ class OAuthProxy(OAuthProvider):
|
|
|
485
498
|
txn_id,
|
|
486
499
|
)
|
|
487
500
|
|
|
501
|
+
# Forward resource parameter if provided (RFC 8707)
|
|
502
|
+
if params.resource:
|
|
503
|
+
query_params["resource"] = params.resource
|
|
504
|
+
logger.debug(
|
|
505
|
+
"Forwarding resource indicator '%s' to upstream for transaction %s",
|
|
506
|
+
params.resource,
|
|
507
|
+
txn_id,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Add any extra authorization parameters configured for this proxy
|
|
511
|
+
if self._extra_authorize_params:
|
|
512
|
+
query_params.update(self._extra_authorize_params)
|
|
513
|
+
logger.debug(
|
|
514
|
+
"Adding extra authorization parameters for transaction %s: %s",
|
|
515
|
+
txn_id,
|
|
516
|
+
list(self._extra_authorize_params.keys()),
|
|
517
|
+
)
|
|
518
|
+
|
|
488
519
|
# Build the upstream authorization URL
|
|
489
520
|
separator = "&" if "?" in self._upstream_authorization_endpoint else "?"
|
|
490
521
|
upstream_url = f"{self._upstream_authorization_endpoint}{separator}{urlencode(query_params)}"
|
|
@@ -870,26 +901,35 @@ class OAuthProxy(OAuthProvider):
|
|
|
870
901
|
f"Exchanging IdP code for tokens with redirect_uri: {idp_redirect_uri}"
|
|
871
902
|
)
|
|
872
903
|
|
|
904
|
+
# Build token exchange parameters
|
|
905
|
+
token_params = {
|
|
906
|
+
"url": self._upstream_token_endpoint,
|
|
907
|
+
"code": idp_code,
|
|
908
|
+
"redirect_uri": idp_redirect_uri,
|
|
909
|
+
}
|
|
910
|
+
|
|
873
911
|
# Include proxy's code_verifier if we forwarded PKCE
|
|
874
912
|
proxy_code_verifier = transaction.get("proxy_code_verifier")
|
|
875
913
|
if proxy_code_verifier:
|
|
914
|
+
token_params["code_verifier"] = proxy_code_verifier
|
|
876
915
|
logger.debug(
|
|
877
916
|
"Including proxy code_verifier in token exchange for transaction %s",
|
|
878
917
|
txn_id,
|
|
879
918
|
)
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
url=self._upstream_token_endpoint,
|
|
889
|
-
code=idp_code,
|
|
890
|
-
redirect_uri=idp_redirect_uri,
|
|
919
|
+
|
|
920
|
+
# Add any extra token parameters configured for this proxy
|
|
921
|
+
if self._extra_token_params:
|
|
922
|
+
token_params.update(self._extra_token_params)
|
|
923
|
+
logger.debug(
|
|
924
|
+
"Adding extra token parameters for transaction %s: %s",
|
|
925
|
+
txn_id,
|
|
926
|
+
list(self._extra_token_params.keys()),
|
|
891
927
|
)
|
|
892
928
|
|
|
929
|
+
idp_tokens: dict[str, Any] = await oauth_client.fetch_token(
|
|
930
|
+
**token_params
|
|
931
|
+
) # type: ignore[misc]
|
|
932
|
+
|
|
893
933
|
logger.debug(
|
|
894
934
|
f"Successfully exchanged IdP code for tokens (transaction: {txn_id}, PKCE: {bool(proxy_code_verifier)})"
|
|
895
935
|
)
|
fastmcp/server/context.py
CHANGED
|
@@ -85,18 +85,18 @@ class Context:
|
|
|
85
85
|
|
|
86
86
|
```python
|
|
87
87
|
@server.tool
|
|
88
|
-
def my_tool(x: int, ctx: Context) -> str:
|
|
88
|
+
async def my_tool(x: int, ctx: Context) -> str:
|
|
89
89
|
# Log messages to the client
|
|
90
|
-
ctx.info(f"Processing {x}")
|
|
91
|
-
ctx.debug("Debug info")
|
|
92
|
-
ctx.warning("Warning message")
|
|
93
|
-
ctx.error("Error message")
|
|
90
|
+
await ctx.info(f"Processing {x}")
|
|
91
|
+
await ctx.debug("Debug info")
|
|
92
|
+
await ctx.warning("Warning message")
|
|
93
|
+
await ctx.error("Error message")
|
|
94
94
|
|
|
95
95
|
# Report progress
|
|
96
|
-
ctx.report_progress(50, 100, "Processing")
|
|
96
|
+
await ctx.report_progress(50, 100, "Processing")
|
|
97
97
|
|
|
98
98
|
# Access resources
|
|
99
|
-
data = ctx.read_resource("resource://data")
|
|
99
|
+
data = await ctx.read_resource("resource://data")
|
|
100
100
|
|
|
101
101
|
# Get request info
|
|
102
102
|
request_id = ctx.request_id
|
|
@@ -449,7 +449,7 @@ class Context:
|
|
|
449
449
|
AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
|
|
450
450
|
): ...
|
|
451
451
|
|
|
452
|
-
"""When response_type is None, the accepted
|
|
452
|
+
"""When response_type is None, the accepted elicitation will contain an
|
|
453
453
|
empty dict"""
|
|
454
454
|
|
|
455
455
|
@overload
|
|
@@ -459,7 +459,7 @@ class Context:
|
|
|
459
459
|
response_type: type[T],
|
|
460
460
|
) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation: ...
|
|
461
461
|
|
|
462
|
-
"""When response_type is not None, the accepted
|
|
462
|
+
"""When response_type is not None, the accepted elicitation will contain the
|
|
463
463
|
response data"""
|
|
464
464
|
|
|
465
465
|
@overload
|
|
@@ -469,7 +469,7 @@ class Context:
|
|
|
469
469
|
response_type: list[str],
|
|
470
470
|
) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation: ...
|
|
471
471
|
|
|
472
|
-
"""When response_type is a list of strings, the accepted
|
|
472
|
+
"""When response_type is a list of strings, the accepted elicitation will
|
|
473
473
|
contain the selected string response"""
|
|
474
474
|
|
|
475
475
|
async def elicit(
|
fastmcp/server/dependencies.py
CHANGED
|
@@ -5,6 +5,9 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from mcp.server.auth.middleware.auth_context import (
|
|
6
6
|
get_access_token as _sdk_get_access_token,
|
|
7
7
|
)
|
|
8
|
+
from mcp.server.auth.provider import (
|
|
9
|
+
AccessToken as _SDKAccessToken,
|
|
10
|
+
)
|
|
8
11
|
from starlette.requests import Request
|
|
9
12
|
|
|
10
13
|
from fastmcp.server.auth import AccessToken
|
|
@@ -107,17 +110,27 @@ def get_access_token() -> AccessToken | None:
|
|
|
107
110
|
The access token if an authenticated user is available, None otherwise.
|
|
108
111
|
"""
|
|
109
112
|
#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
access_token: _SDKAccessToken | None = _sdk_get_access_token()
|
|
114
|
+
|
|
115
|
+
if access_token is None or isinstance(access_token, AccessToken):
|
|
116
|
+
return access_token
|
|
113
117
|
|
|
114
118
|
# If the object is not a FastMCP AccessToken, convert it to one if the fields are compatible
|
|
115
119
|
# This is a workaround for the case where the SDK returns a different type
|
|
116
120
|
# If it fails, it will raise a TypeError
|
|
117
121
|
try:
|
|
118
|
-
|
|
122
|
+
access_token_as_dict = access_token.model_dump()
|
|
123
|
+
return AccessToken(
|
|
124
|
+
token=access_token_as_dict["token"],
|
|
125
|
+
client_id=access_token_as_dict["client_id"],
|
|
126
|
+
scopes=access_token_as_dict["scopes"],
|
|
127
|
+
# Optional fields
|
|
128
|
+
expires_at=access_token_as_dict.get("expires_at"),
|
|
129
|
+
resource_owner=access_token_as_dict.get("resource_owner"),
|
|
130
|
+
claims=access_token_as_dict.get("claims"),
|
|
131
|
+
)
|
|
119
132
|
except Exception as e:
|
|
120
133
|
raise TypeError(
|
|
121
|
-
f"Expected fastmcp.server.auth.auth.AccessToken, got {type(
|
|
134
|
+
f"Expected fastmcp.server.auth.auth.AccessToken, got {type(access_token).__name__}. "
|
|
122
135
|
"Ensure the SDK is using the correct AccessToken type."
|
|
123
136
|
) from e
|
fastmcp/server/server.py
CHANGED
|
@@ -812,6 +812,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
812
812
|
|
|
813
813
|
Delegates to _get_prompt, which should be overridden by FastMCP subclasses.
|
|
814
814
|
"""
|
|
815
|
+
import fastmcp.server.context
|
|
816
|
+
|
|
815
817
|
logger.debug(
|
|
816
818
|
f"[{self.name}] Handler called: get_prompt %s with %s", name, arguments
|
|
817
819
|
)
|
|
@@ -1208,8 +1210,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1208
1210
|
return f"Weather for {city}"
|
|
1209
1211
|
|
|
1210
1212
|
@server.resource("resource://{city}/weather")
|
|
1211
|
-
def get_weather_with_context(city: str, ctx: Context) -> str:
|
|
1212
|
-
ctx.info(f"Fetching weather for {city}")
|
|
1213
|
+
async def get_weather_with_context(city: str, ctx: Context) -> str:
|
|
1214
|
+
await ctx.info(f"Fetching weather for {city}")
|
|
1213
1215
|
return f"Weather for {city}"
|
|
1214
1216
|
|
|
1215
1217
|
@server.resource("resource://{city}/weather")
|
|
@@ -1384,8 +1386,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1384
1386
|
]
|
|
1385
1387
|
|
|
1386
1388
|
@server.prompt()
|
|
1387
|
-
def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
|
|
1388
|
-
ctx.info(f"Analyzing table {table_name}")
|
|
1389
|
+
async def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
|
|
1390
|
+
await ctx.info(f"Analyzing table {table_name}")
|
|
1389
1391
|
schema = read_table_schema(table_name)
|
|
1390
1392
|
return [
|
|
1391
1393
|
{
|
|
@@ -1395,7 +1397,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1395
1397
|
]
|
|
1396
1398
|
|
|
1397
1399
|
@server.prompt("custom_name")
|
|
1398
|
-
def analyze_file(path: str) -> list[Message]:
|
|
1400
|
+
async def analyze_file(path: str) -> list[Message]:
|
|
1399
1401
|
content = await read_file(path)
|
|
1400
1402
|
return [
|
|
1401
1403
|
{
|
fastmcp/settings.py
CHANGED
|
@@ -272,7 +272,7 @@ class Settings(BaseSettings):
|
|
|
272
272
|
|
|
273
273
|
If None, no automatic configuration will take place.
|
|
274
274
|
|
|
275
|
-
This setting is *always*
|
|
275
|
+
This setting is *always* overridden by any auth provider passed to the
|
|
276
276
|
FastMCP constructor.
|
|
277
277
|
|
|
278
278
|
Note that most auth providers require additional configuration
|
|
@@ -343,6 +343,20 @@ class Settings(BaseSettings):
|
|
|
343
343
|
),
|
|
344
344
|
] = False
|
|
345
345
|
|
|
346
|
+
show_cli_banner: Annotated[
|
|
347
|
+
bool,
|
|
348
|
+
Field(
|
|
349
|
+
default=True,
|
|
350
|
+
description=inspect.cleandoc(
|
|
351
|
+
"""
|
|
352
|
+
If True, the server banner will be displayed when running the server via CLI.
|
|
353
|
+
This setting can be overridden by the --no-banner CLI flag.
|
|
354
|
+
Set to False via FASTMCP_SHOW_CLI_BANNER=false to suppress the banner.
|
|
355
|
+
"""
|
|
356
|
+
),
|
|
357
|
+
),
|
|
358
|
+
] = True
|
|
359
|
+
|
|
346
360
|
|
|
347
361
|
def __getattr__(name: str):
|
|
348
362
|
"""
|