fastmcp 2.13.1__py3-none-any.whl → 2.13.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/server/auth/oauth_proxy.py +152 -85
- fastmcp/server/auth/oidc_proxy.py +31 -3
- fastmcp/server/auth/providers/azure.py +96 -10
- fastmcp/server/auth/providers/descope.py +82 -23
- fastmcp/server/auth/providers/discord.py +308 -0
- fastmcp/server/auth/providers/google.py +18 -0
- fastmcp/server/auth/providers/scalekit.py +76 -17
- fastmcp/server/dependencies.py +26 -4
- fastmcp/server/proxy.py +10 -0
- fastmcp/server/server.py +4 -1
- fastmcp/tools/tool.py +19 -1
- fastmcp/tools/tool_transform.py +3 -1
- fastmcp/utilities/types.py +49 -0
- fastmcp/utilities/ui.py +11 -2
- {fastmcp-2.13.1.dist-info → fastmcp-2.13.3.dist-info}/METADATA +4 -3
- {fastmcp-2.13.1.dist-info → fastmcp-2.13.3.dist-info}/RECORD +19 -18
- {fastmcp-2.13.1.dist-info → fastmcp-2.13.3.dist-info}/WHEEL +1 -1
- {fastmcp-2.13.1.dist-info → fastmcp-2.13.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.1.dist-info → fastmcp-2.13.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,7 +8,7 @@ authentication for seamless MCP client authentication.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
|
-
from pydantic import AnyHttpUrl
|
|
11
|
+
from pydantic import AnyHttpUrl, field_validator, model_validator
|
|
12
12
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
13
13
|
from starlette.responses import JSONResponse
|
|
14
14
|
from starlette.routing import Route
|
|
@@ -16,6 +16,7 @@ from starlette.routing import Route
|
|
|
16
16
|
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
|
|
17
17
|
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
|
18
18
|
from fastmcp.settings import ENV_FILE
|
|
19
|
+
from fastmcp.utilities.auth import parse_scopes
|
|
19
20
|
from fastmcp.utilities.logging import get_logger
|
|
20
21
|
from fastmcp.utilities.types import NotSet, NotSetT
|
|
21
22
|
|
|
@@ -30,9 +31,25 @@ class ScalekitProviderSettings(BaseSettings):
|
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
environment_url: AnyHttpUrl
|
|
33
|
-
client_id: str
|
|
34
34
|
resource_id: str
|
|
35
|
-
|
|
35
|
+
base_url: AnyHttpUrl | None = None
|
|
36
|
+
mcp_url: AnyHttpUrl | None = None
|
|
37
|
+
required_scopes: list[str] | None = None
|
|
38
|
+
|
|
39
|
+
@field_validator("required_scopes", mode="before")
|
|
40
|
+
@classmethod
|
|
41
|
+
def _parse_scopes(cls, value: object):
|
|
42
|
+
return parse_scopes(value)
|
|
43
|
+
|
|
44
|
+
@model_validator(mode="after")
|
|
45
|
+
def _resolve_base_url(self):
|
|
46
|
+
resolved = self.base_url or self.mcp_url
|
|
47
|
+
if resolved is None:
|
|
48
|
+
msg = "Either base_url or mcp_url must be provided for ScalekitProvider"
|
|
49
|
+
raise ValueError(msg)
|
|
50
|
+
|
|
51
|
+
object.__setattr__(self, "base_url", resolved)
|
|
52
|
+
return self
|
|
36
53
|
|
|
37
54
|
|
|
38
55
|
class ScalekitProvider(RemoteAuthProvider):
|
|
@@ -53,9 +70,8 @@ class ScalekitProvider(RemoteAuthProvider):
|
|
|
53
70
|
|
|
54
71
|
2. Environment Configuration:
|
|
55
72
|
- Set SCALEKIT_ENVIRONMENT_URL (e.g., https://your-env.scalekit.com)
|
|
56
|
-
- Set SCALEKIT_CLIENT_ID from your OAuth application
|
|
57
73
|
- Set SCALEKIT_RESOURCE_ID from your created resource
|
|
58
|
-
- Set
|
|
74
|
+
- Set BASE_URL to your FastMCP server's public URL
|
|
59
75
|
|
|
60
76
|
For detailed setup instructions, see:
|
|
61
77
|
https://docs.scalekit.com/mcp/overview/
|
|
@@ -67,9 +83,8 @@ class ScalekitProvider(RemoteAuthProvider):
|
|
|
67
83
|
# Create Scalekit resource server provider
|
|
68
84
|
scalekit_auth = ScalekitProvider(
|
|
69
85
|
environment_url="https://your-env.scalekit.com",
|
|
70
|
-
client_id="sk_client_...",
|
|
71
86
|
resource_id="sk_resource_...",
|
|
72
|
-
|
|
87
|
+
base_url="https://your-fastmcp-server.com",
|
|
73
88
|
)
|
|
74
89
|
|
|
75
90
|
# Use with FastMCP
|
|
@@ -83,44 +98,77 @@ class ScalekitProvider(RemoteAuthProvider):
|
|
|
83
98
|
environment_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
84
99
|
client_id: str | NotSetT = NotSet,
|
|
85
100
|
resource_id: str | NotSetT = NotSet,
|
|
101
|
+
base_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
86
102
|
mcp_url: AnyHttpUrl | str | NotSetT = NotSet,
|
|
103
|
+
required_scopes: list[str] | NotSetT = NotSet,
|
|
87
104
|
token_verifier: TokenVerifier | None = None,
|
|
88
105
|
):
|
|
89
106
|
"""Initialize Scalekit resource server provider.
|
|
90
107
|
|
|
91
108
|
Args:
|
|
92
109
|
environment_url: Your Scalekit environment URL (e.g., "https://your-env.scalekit.com")
|
|
93
|
-
client_id: Your Scalekit OAuth client ID
|
|
94
110
|
resource_id: Your Scalekit resource ID
|
|
95
|
-
|
|
111
|
+
base_url: Public URL of this FastMCP server
|
|
112
|
+
required_scopes: Optional list of scopes that must be present in tokens
|
|
96
113
|
token_verifier: Optional token verifier. If None, creates JWT verifier for Scalekit
|
|
97
114
|
"""
|
|
115
|
+
legacy_client_id = client_id is not NotSet
|
|
116
|
+
|
|
98
117
|
settings = ScalekitProviderSettings.model_validate(
|
|
99
118
|
{
|
|
100
119
|
k: v
|
|
101
120
|
for k, v in {
|
|
102
121
|
"environment_url": environment_url,
|
|
103
|
-
"client_id": client_id,
|
|
104
122
|
"resource_id": resource_id,
|
|
123
|
+
"base_url": base_url,
|
|
105
124
|
"mcp_url": mcp_url,
|
|
125
|
+
"required_scopes": required_scopes,
|
|
106
126
|
}.items()
|
|
107
127
|
if v is not NotSet
|
|
108
128
|
}
|
|
109
129
|
)
|
|
110
130
|
|
|
131
|
+
if settings.mcp_url is not None:
|
|
132
|
+
logger.warning(
|
|
133
|
+
"ScalekitProvider parameter 'mcp_url' is deprecated and will be removed in a future release. "
|
|
134
|
+
"Rename it to 'base_url'."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if legacy_client_id:
|
|
138
|
+
logger.warning(
|
|
139
|
+
"ScalekitProvider no longer requires 'client_id'. The parameter is accepted only for backward "
|
|
140
|
+
"compatibility and will be removed in a future release."
|
|
141
|
+
)
|
|
142
|
+
|
|
111
143
|
self.environment_url = str(settings.environment_url).rstrip("/")
|
|
112
|
-
self.client_id = settings.client_id
|
|
113
144
|
self.resource_id = settings.resource_id
|
|
114
|
-
self.
|
|
145
|
+
self.required_scopes = settings.required_scopes or []
|
|
146
|
+
base_url_value = str(settings.base_url)
|
|
147
|
+
|
|
148
|
+
logger.debug(
|
|
149
|
+
"Initializing ScalekitProvider: environment_url=%s resource_id=%s base_url=%s required_scopes=%s",
|
|
150
|
+
self.environment_url,
|
|
151
|
+
self.resource_id,
|
|
152
|
+
base_url_value,
|
|
153
|
+
self.required_scopes,
|
|
154
|
+
)
|
|
115
155
|
|
|
116
156
|
# Create default JWT verifier if none provided
|
|
117
157
|
if token_verifier is None:
|
|
158
|
+
logger.debug(
|
|
159
|
+
"Creating default JWTVerifier for Scalekit: jwks_uri=%s issuer=%s required_scopes=%s",
|
|
160
|
+
f"{self.environment_url}/keys",
|
|
161
|
+
self.environment_url,
|
|
162
|
+
self.required_scopes,
|
|
163
|
+
)
|
|
118
164
|
token_verifier = JWTVerifier(
|
|
119
165
|
jwks_uri=f"{self.environment_url}/keys",
|
|
120
166
|
issuer=self.environment_url,
|
|
121
167
|
algorithm="RS256",
|
|
122
|
-
|
|
168
|
+
required_scopes=self.required_scopes or None,
|
|
123
169
|
)
|
|
170
|
+
else:
|
|
171
|
+
logger.debug("Using custom token verifier for ScalekitProvider")
|
|
124
172
|
|
|
125
173
|
# Initialize RemoteAuthProvider with Scalekit as the authorization server
|
|
126
174
|
super().__init__(
|
|
@@ -128,7 +176,7 @@ class ScalekitProvider(RemoteAuthProvider):
|
|
|
128
176
|
authorization_servers=[
|
|
129
177
|
AnyHttpUrl(f"{self.environment_url}/resources/{self.resource_id}")
|
|
130
178
|
],
|
|
131
|
-
base_url=
|
|
179
|
+
base_url=base_url_value,
|
|
132
180
|
)
|
|
133
181
|
|
|
134
182
|
def get_routes(
|
|
@@ -146,16 +194,27 @@ class ScalekitProvider(RemoteAuthProvider):
|
|
|
146
194
|
"""
|
|
147
195
|
# Get the standard protected resource routes from RemoteAuthProvider
|
|
148
196
|
routes = super().get_routes(mcp_path)
|
|
197
|
+
logger.debug(
|
|
198
|
+
"Preparing Scalekit metadata routes: mcp_path=%s resource_id=%s",
|
|
199
|
+
mcp_path,
|
|
200
|
+
self.resource_id,
|
|
201
|
+
)
|
|
149
202
|
|
|
150
203
|
async def oauth_authorization_server_metadata(request):
|
|
151
204
|
"""Forward Scalekit OAuth authorization server metadata with FastMCP customizations."""
|
|
152
205
|
try:
|
|
206
|
+
metadata_url = f"{self.environment_url}/.well-known/oauth-authorization-server/resources/{self.resource_id}"
|
|
207
|
+
logger.debug(
|
|
208
|
+
"Fetching Scalekit OAuth metadata: metadata_url=%s", metadata_url
|
|
209
|
+
)
|
|
153
210
|
async with httpx.AsyncClient() as client:
|
|
154
|
-
response = await client.get(
|
|
155
|
-
f"{self.environment_url}/.well-known/oauth-authorization-server/resources/{self.resource_id}"
|
|
156
|
-
)
|
|
211
|
+
response = await client.get(metadata_url)
|
|
157
212
|
response.raise_for_status()
|
|
158
213
|
metadata = response.json()
|
|
214
|
+
logger.debug(
|
|
215
|
+
"Scalekit metadata fetched successfully: metadata_keys=%s",
|
|
216
|
+
list(metadata.keys()),
|
|
217
|
+
)
|
|
159
218
|
return JSONResponse(metadata)
|
|
160
219
|
except Exception as e:
|
|
161
220
|
logger.error(f"Failed to fetch Scalekit metadata: {e}")
|
fastmcp/server/dependencies.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
from mcp.server.auth.middleware.auth_context import (
|
|
7
7
|
get_access_token as _sdk_get_access_token,
|
|
8
8
|
)
|
|
9
|
+
from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser
|
|
9
10
|
from mcp.server.auth.provider import (
|
|
10
11
|
AccessToken as _SDKAccessToken,
|
|
11
12
|
)
|
|
@@ -111,17 +112,38 @@ def get_access_token() -> AccessToken | None:
|
|
|
111
112
|
"""
|
|
112
113
|
Get the FastMCP access token from the current context.
|
|
113
114
|
|
|
115
|
+
This function first tries to get the token from the current HTTP request's scope,
|
|
116
|
+
which is more reliable for long-lived connections where the SDK's auth_context_var
|
|
117
|
+
may become stale after token refresh. Falls back to the SDK's context var if no
|
|
118
|
+
request is available.
|
|
119
|
+
|
|
114
120
|
Returns:
|
|
115
121
|
The access token if an authenticated user is available, None otherwise.
|
|
116
122
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
access_token: _SDKAccessToken | None = None
|
|
124
|
+
|
|
125
|
+
# First, try to get from current HTTP request's scope (issue #1863)
|
|
126
|
+
# This is more reliable than auth_context_var for Streamable HTTP sessions
|
|
127
|
+
# where tokens may be refreshed between MCP messages
|
|
128
|
+
try:
|
|
129
|
+
request = get_http_request()
|
|
130
|
+
user = request.scope.get("user")
|
|
131
|
+
if isinstance(user, AuthenticatedUser):
|
|
132
|
+
access_token = user.access_token
|
|
133
|
+
except RuntimeError:
|
|
134
|
+
# No HTTP request available, fall back to context var
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
# Fall back to SDK's context var if we didn't get a token from the request
|
|
138
|
+
if access_token is None:
|
|
139
|
+
access_token = _sdk_get_access_token()
|
|
119
140
|
|
|
120
141
|
if access_token is None or isinstance(access_token, AccessToken):
|
|
121
142
|
return access_token
|
|
122
143
|
|
|
123
|
-
# If the object is not a FastMCP AccessToken, convert it to one if the
|
|
124
|
-
#
|
|
144
|
+
# If the object is not a FastMCP AccessToken, convert it to one if the
|
|
145
|
+
# fields are compatible (e.g. `claims` is not present in the SDK's AccessToken).
|
|
146
|
+
# This is a workaround for the case where the SDK or auth provider returns a different type
|
|
125
147
|
# If it fails, it will raise a TypeError
|
|
126
148
|
try:
|
|
127
149
|
access_token_as_dict = access_token.model_dump()
|
fastmcp/server/proxy.py
CHANGED
|
@@ -270,10 +270,12 @@ class ProxyTool(Tool, MirroredComponent):
|
|
|
270
270
|
return cls(
|
|
271
271
|
client=client,
|
|
272
272
|
name=mcp_tool.name,
|
|
273
|
+
title=mcp_tool.title,
|
|
273
274
|
description=mcp_tool.description,
|
|
274
275
|
parameters=mcp_tool.inputSchema,
|
|
275
276
|
annotations=mcp_tool.annotations,
|
|
276
277
|
output_schema=mcp_tool.outputSchema,
|
|
278
|
+
icons=mcp_tool.icons,
|
|
277
279
|
meta=mcp_tool.meta,
|
|
278
280
|
tags=(mcp_tool.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
279
281
|
_mirrored=True,
|
|
@@ -329,8 +331,10 @@ class ProxyResource(Resource, MirroredComponent):
|
|
|
329
331
|
client=client,
|
|
330
332
|
uri=mcp_resource.uri,
|
|
331
333
|
name=mcp_resource.name,
|
|
334
|
+
title=mcp_resource.title,
|
|
332
335
|
description=mcp_resource.description,
|
|
333
336
|
mime_type=mcp_resource.mimeType or "text/plain",
|
|
337
|
+
icons=mcp_resource.icons,
|
|
334
338
|
meta=mcp_resource.meta,
|
|
335
339
|
tags=(mcp_resource.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
336
340
|
_mirrored=True,
|
|
@@ -369,8 +373,10 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
|
|
|
369
373
|
client=client,
|
|
370
374
|
uri_template=mcp_template.uriTemplate,
|
|
371
375
|
name=mcp_template.name,
|
|
376
|
+
title=mcp_template.title,
|
|
372
377
|
description=mcp_template.description,
|
|
373
378
|
mime_type=mcp_template.mimeType or "text/plain",
|
|
379
|
+
icons=mcp_template.icons,
|
|
374
380
|
parameters={}, # Remote templates don't have local parameters
|
|
375
381
|
meta=mcp_template.meta,
|
|
376
382
|
tags=(mcp_template.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
@@ -404,8 +410,10 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
|
|
|
404
410
|
client=self._client,
|
|
405
411
|
uri=parameterized_uri,
|
|
406
412
|
name=self.name,
|
|
413
|
+
title=self.title,
|
|
407
414
|
description=self.description,
|
|
408
415
|
mime_type=result[0].mimeType,
|
|
416
|
+
icons=self.icons,
|
|
409
417
|
meta=self.meta,
|
|
410
418
|
tags=(self.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
411
419
|
_value=value,
|
|
@@ -439,8 +447,10 @@ class ProxyPrompt(Prompt, MirroredComponent):
|
|
|
439
447
|
return cls(
|
|
440
448
|
client=client,
|
|
441
449
|
name=mcp_prompt.name,
|
|
450
|
+
title=mcp_prompt.title,
|
|
442
451
|
description=mcp_prompt.description,
|
|
443
452
|
arguments=arguments,
|
|
453
|
+
icons=mcp_prompt.icons,
|
|
444
454
|
meta=mcp_prompt.meta,
|
|
445
455
|
tags=(mcp_prompt.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
446
456
|
_mirrored=True,
|
fastmcp/server/server.py
CHANGED
|
@@ -1434,7 +1434,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1434
1434
|
tags: Optional set of tags for categorizing the tool
|
|
1435
1435
|
output_schema: Optional JSON schema for the tool's output
|
|
1436
1436
|
annotations: Optional annotations about the tool's behavior
|
|
1437
|
-
exclude_args: Optional list of argument names to exclude from the tool schema
|
|
1437
|
+
exclude_args: Optional list of argument names to exclude from the tool schema.
|
|
1438
|
+
Note: `exclude_args` will be deprecated in FastMCP 2.14 in favor of dependency
|
|
1439
|
+
injection with `Depends()` for better lifecycle management.
|
|
1438
1440
|
meta: Optional meta information about the tool
|
|
1439
1441
|
enabled: Optional boolean to enable or disable the tool
|
|
1440
1442
|
|
|
@@ -1485,6 +1487,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1485
1487
|
tool_name = name # Use keyword name if provided, otherwise None
|
|
1486
1488
|
|
|
1487
1489
|
# Register the tool immediately and return the tool object
|
|
1490
|
+
# Note: Deprecation warning for exclude_args is handled in Tool.from_function
|
|
1488
1491
|
tool = Tool.from_function(
|
|
1489
1492
|
fn,
|
|
1490
1493
|
name=tool_name,
|
fastmcp/tools/tool.py
CHANGED
|
@@ -32,6 +32,7 @@ from fastmcp.utilities.types import (
|
|
|
32
32
|
Image,
|
|
33
33
|
NotSet,
|
|
34
34
|
NotSetT,
|
|
35
|
+
create_function_without_params,
|
|
35
36
|
find_kwarg_by_type,
|
|
36
37
|
get_cached_typeadapter,
|
|
37
38
|
replace_type,
|
|
@@ -271,6 +272,16 @@ class FunctionTool(Tool):
|
|
|
271
272
|
enabled: bool | None = None,
|
|
272
273
|
) -> FunctionTool:
|
|
273
274
|
"""Create a Tool from a function."""
|
|
275
|
+
if exclude_args and fastmcp.settings.deprecation_warnings:
|
|
276
|
+
warnings.warn(
|
|
277
|
+
"The `exclude_args` parameter will be deprecated in FastMCP 2.14. "
|
|
278
|
+
"We recommend using dependency injection with `Depends()` instead, which provides "
|
|
279
|
+
"better lifecycle management and is more explicit. "
|
|
280
|
+
"`exclude_args` will continue to work until then. "
|
|
281
|
+
"See https://gofastmcp.com/docs/servers/tools for examples.",
|
|
282
|
+
DeprecationWarning,
|
|
283
|
+
stacklevel=2,
|
|
284
|
+
)
|
|
274
285
|
|
|
275
286
|
parsed_fn = ParsedFunction.from_function(fn, exclude_args=exclude_args)
|
|
276
287
|
|
|
@@ -442,7 +453,14 @@ class ParsedFunction:
|
|
|
442
453
|
if exclude_args:
|
|
443
454
|
prune_params.extend(exclude_args)
|
|
444
455
|
|
|
445
|
-
|
|
456
|
+
# Create a function without excluded parameters in annotations
|
|
457
|
+
# This prevents Pydantic from trying to serialize non-serializable types
|
|
458
|
+
# before we can exclude them in compress_schema
|
|
459
|
+
fn_for_typeadapter = fn
|
|
460
|
+
if prune_params:
|
|
461
|
+
fn_for_typeadapter = create_function_without_params(fn, prune_params)
|
|
462
|
+
|
|
463
|
+
input_type_adapter = get_cached_typeadapter(fn_for_typeadapter)
|
|
446
464
|
input_schema = input_type_adapter.json_schema()
|
|
447
465
|
input_schema = compress_schema(
|
|
448
466
|
input_schema, prune_params=prune_params, prune_titles=True
|
fastmcp/tools/tool_transform.py
CHANGED
|
@@ -4,6 +4,7 @@ import inspect
|
|
|
4
4
|
import warnings
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from contextvars import ContextVar
|
|
7
|
+
from copy import deepcopy
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from typing import Annotated, Any, Literal, cast
|
|
9
10
|
|
|
@@ -620,7 +621,8 @@ class TransformedTool(Tool):
|
|
|
620
621
|
"""
|
|
621
622
|
|
|
622
623
|
# Build transformed schema and mapping
|
|
623
|
-
|
|
624
|
+
# Deep copy to prevent compress_schema from mutating parent tool's $defs
|
|
625
|
+
parent_defs = deepcopy(parent_tool.parameters.get("$defs", {}))
|
|
624
626
|
parent_props = parent_tool.parameters.get("properties", {}).copy()
|
|
625
627
|
parent_required = set(parent_tool.parameters.get("required", []))
|
|
626
628
|
|
fastmcp/utilities/types.py
CHANGED
|
@@ -175,6 +175,55 @@ def find_kwarg_by_type(fn: Callable, kwarg_type: type) -> str | None:
|
|
|
175
175
|
return None
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
def create_function_without_params(
|
|
179
|
+
fn: Callable[..., Any], exclude_params: list[str]
|
|
180
|
+
) -> Callable[..., Any]:
|
|
181
|
+
"""
|
|
182
|
+
Create a new function with the same code but without the specified parameters in annotations.
|
|
183
|
+
|
|
184
|
+
This is used to exclude parameters from type adapter processing when they can't be serialized.
|
|
185
|
+
The excluded parameters are removed from the function's __annotations__ dictionary.
|
|
186
|
+
"""
|
|
187
|
+
import types
|
|
188
|
+
|
|
189
|
+
if inspect.ismethod(fn):
|
|
190
|
+
actual_func = fn.__func__
|
|
191
|
+
code = actual_func.__code__ # ty: ignore[unresolved-attribute]
|
|
192
|
+
globals_dict = actual_func.__globals__ # ty: ignore[unresolved-attribute]
|
|
193
|
+
name = actual_func.__name__ # ty: ignore[unresolved-attribute]
|
|
194
|
+
defaults = actual_func.__defaults__ # ty: ignore[unresolved-attribute]
|
|
195
|
+
closure = actual_func.__closure__ # ty: ignore[unresolved-attribute]
|
|
196
|
+
else:
|
|
197
|
+
code = fn.__code__ # ty: ignore[unresolved-attribute]
|
|
198
|
+
globals_dict = fn.__globals__ # ty: ignore[unresolved-attribute]
|
|
199
|
+
name = fn.__name__ # ty: ignore[unresolved-attribute]
|
|
200
|
+
defaults = fn.__defaults__ # ty: ignore[unresolved-attribute]
|
|
201
|
+
closure = fn.__closure__ # ty: ignore[unresolved-attribute]
|
|
202
|
+
|
|
203
|
+
# Create a copy of annotations without the excluded parameters
|
|
204
|
+
original_annotations = getattr(fn, "__annotations__", {})
|
|
205
|
+
new_annotations = {
|
|
206
|
+
k: v for k, v in original_annotations.items() if k not in exclude_params
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
new_func = types.FunctionType(
|
|
210
|
+
code,
|
|
211
|
+
globals_dict,
|
|
212
|
+
name,
|
|
213
|
+
defaults,
|
|
214
|
+
closure,
|
|
215
|
+
)
|
|
216
|
+
new_func.__dict__.update(fn.__dict__)
|
|
217
|
+
new_func.__module__ = fn.__module__
|
|
218
|
+
new_func.__qualname__ = getattr(fn, "__qualname__", fn.__name__) # ty: ignore[unresolved-attribute]
|
|
219
|
+
new_func.__annotations__ = new_annotations
|
|
220
|
+
|
|
221
|
+
if inspect.ismethod(fn):
|
|
222
|
+
return types.MethodType(new_func, fn.__self__)
|
|
223
|
+
else:
|
|
224
|
+
return new_func
|
|
225
|
+
|
|
226
|
+
|
|
178
227
|
class Image:
|
|
179
228
|
"""Helper class for returning images from tools."""
|
|
180
229
|
|
fastmcp/utilities/ui.py
CHANGED
|
@@ -463,12 +463,21 @@ def create_page(
|
|
|
463
463
|
content: HTML content to place inside the page
|
|
464
464
|
title: Page title
|
|
465
465
|
additional_styles: Extra CSS to include
|
|
466
|
-
csp_policy: Content Security Policy header value
|
|
466
|
+
csp_policy: Content Security Policy header value.
|
|
467
|
+
If empty string "", the CSP meta tag is omitted entirely.
|
|
467
468
|
|
|
468
469
|
Returns:
|
|
469
470
|
Complete HTML page as string
|
|
470
471
|
"""
|
|
471
472
|
title = html.escape(title)
|
|
473
|
+
|
|
474
|
+
# Only include CSP meta tag if policy is non-empty
|
|
475
|
+
csp_meta = (
|
|
476
|
+
f'<meta http-equiv="Content-Security-Policy" content="{html.escape(csp_policy, quote=True)}" />'
|
|
477
|
+
if csp_policy
|
|
478
|
+
else ""
|
|
479
|
+
)
|
|
480
|
+
|
|
472
481
|
return f"""
|
|
473
482
|
<!DOCTYPE html>
|
|
474
483
|
<html lang="en">
|
|
@@ -480,7 +489,7 @@ def create_page(
|
|
|
480
489
|
{BASE_STYLES}
|
|
481
490
|
{additional_styles}
|
|
482
491
|
</style>
|
|
483
|
-
|
|
492
|
+
{csp_meta}
|
|
484
493
|
</head>
|
|
485
494
|
<body>
|
|
486
495
|
{content}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.3
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers and clients.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -23,10 +23,10 @@ Requires-Dist: cyclopts>=4.0.0
|
|
|
23
23
|
Requires-Dist: exceptiongroup>=1.2.2
|
|
24
24
|
Requires-Dist: httpx>=0.28.1
|
|
25
25
|
Requires-Dist: jsonschema-path>=0.3.4
|
|
26
|
-
Requires-Dist: mcp!=1.21.1,<
|
|
26
|
+
Requires-Dist: mcp!=1.21.1,<1.23,>=1.19.0
|
|
27
27
|
Requires-Dist: openapi-pydantic>=0.5.1
|
|
28
28
|
Requires-Dist: platformdirs>=4.0.0
|
|
29
|
-
Requires-Dist: py-key-value-aio[disk,
|
|
29
|
+
Requires-Dist: py-key-value-aio[disk,memory]<0.4.0,>=0.2.8
|
|
30
30
|
Requires-Dist: pydantic[email]>=2.11.7
|
|
31
31
|
Requires-Dist: pyperclip>=1.9.0
|
|
32
32
|
Requires-Dist: python-dotenv>=1.1.0
|
|
@@ -355,6 +355,7 @@ FastMCP provides comprehensive authentication support that sets it apart from ba
|
|
|
355
355
|
- **Auth0**
|
|
356
356
|
- **WorkOS**
|
|
357
357
|
- **Descope**
|
|
358
|
+
- **Discord**
|
|
358
359
|
- **JWT/Custom**
|
|
359
360
|
- **API Keys**
|
|
360
361
|
|
|
@@ -67,35 +67,36 @@ fastmcp/resources/template.py,sha256=vu9InVUKc5CvEOUvlTXsZ8-tpet_-kf8yX-rNrxE4Pw
|
|
|
67
67
|
fastmcp/resources/types.py,sha256=efFLGD1Xc5Xq3sxlPaZ_8gtJ2UOixueTBV4KQTi4cOU,4936
|
|
68
68
|
fastmcp/server/__init__.py,sha256=qxNmIJcqsrpxpUvCv0mhdEAaUn1UZd1xLd8XRoWUlfY,119
|
|
69
69
|
fastmcp/server/context.py,sha256=TYPUb7zr2rnNNmZSvaKbWy0J94dhdsL210eWl1HMPzQ,28650
|
|
70
|
-
fastmcp/server/dependencies.py,sha256=
|
|
70
|
+
fastmcp/server/dependencies.py,sha256=gbtI6vqAH-u-TV9tbXZUcYisMCyUHs_-Y_PXcFxx5w4,5292
|
|
71
71
|
fastmcp/server/elicitation.py,sha256=WYsj-H9U-t3b6awcLUWl1b1EA5X48Ef6_kvLhxYgYGs,8777
|
|
72
72
|
fastmcp/server/http.py,sha256=IMggGikJxIMg1CkHH1du3BiKtbD2SU4wDyS0xvLG1O8,12032
|
|
73
73
|
fastmcp/server/low_level.py,sha256=b1Sx0_Py0QxeLXSLdDA5PjR9Dd9ANB7KSNkkGSr1AeE,5490
|
|
74
74
|
fastmcp/server/openapi.py,sha256=xWiQC3mjk81G7ZWhXF3PpECuCM1arD2tiHF9EM9kUyU,42332
|
|
75
|
-
fastmcp/server/proxy.py,sha256=
|
|
76
|
-
fastmcp/server/server.py,sha256=
|
|
75
|
+
fastmcp/server/proxy.py,sha256=iGyBPPnhtzOxuTknv-OZEdzelgEF_MOBgI3RxEMxopI,26157
|
|
76
|
+
fastmcp/server/server.py,sha256=G9Qesj1_p0AYvnqTA89dun2p1nwYMl1O-PMksZb93k8,109753
|
|
77
77
|
fastmcp/server/auth/__init__.py,sha256=1VZ3MhZhlByvo7QCWT1tMIdRdMppUw4_TeqclSPJeiI,820
|
|
78
78
|
fastmcp/server/auth/auth.py,sha256=YjRM4zHvTlAfQYB97HEnXfCgOXA60Kcrb5O2nHsyka8,14524
|
|
79
79
|
fastmcp/server/auth/jwt_issuer.py,sha256=lJYvrpC1ygI4jkoJlL_nTH6m7FKdTw2lbEycKo4eHLY,7197
|
|
80
80
|
fastmcp/server/auth/middleware.py,sha256=xwj3fUCLSlJK6n1Ehp-FN1qnjKqEz8b7LGAGMTqQ8Hk,3284
|
|
81
|
-
fastmcp/server/auth/oauth_proxy.py,sha256=
|
|
82
|
-
fastmcp/server/auth/oidc_proxy.py,sha256
|
|
81
|
+
fastmcp/server/auth/oauth_proxy.py,sha256=onInFJmGOxlAk2iZhug2AfncDFo3owkd1QbA6UQo_n0,92482
|
|
82
|
+
fastmcp/server/auth/oidc_proxy.py,sha256=1VsNrTeBNnVm_yO5JPoIig-XvAynnsnFBwhaIx4fywE,17414
|
|
83
83
|
fastmcp/server/auth/redirect_validation.py,sha256=Jlhela9xpTbw4aWnQ04A5Z-TW0HYOC3f9BMsq3NXx1Q,2000
|
|
84
84
|
fastmcp/server/auth/handlers/authorize.py,sha256=1zrmXqRUhjiWSHgUhfj0CcCkj3uSlGkTnxHzaic0xYs,11617
|
|
85
85
|
fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
86
|
fastmcp/server/auth/providers/auth0.py,sha256=dZkc7hppii20YWota_6_Y3vdNw-DZSq0OyModbly-RA,7814
|
|
87
87
|
fastmcp/server/auth/providers/aws.py,sha256=MXoEEnXmeIlRjaHqTeNCmJ90iTx9jwUdEdpyLUmzfIc,10852
|
|
88
|
-
fastmcp/server/auth/providers/azure.py,sha256=
|
|
88
|
+
fastmcp/server/auth/providers/azure.py,sha256=Lq949keq4-AC7AR6Dbn5caEim5XOAK3WpnB-GmRPLtY,20891
|
|
89
89
|
fastmcp/server/auth/providers/bearer.py,sha256=LwkCfDJS48BxBxZwrIrauqNfCtDrJtGqYyEWnJjUq7s,923
|
|
90
90
|
fastmcp/server/auth/providers/debug.py,sha256=92erHZGQB1ATsl6PwrXui6h3WJ4wLxE9ACbI3JutmWY,3881
|
|
91
|
-
fastmcp/server/auth/providers/descope.py,sha256=
|
|
91
|
+
fastmcp/server/auth/providers/descope.py,sha256=y3PX3RmEL-JzHktKUbRW25QPZ7AVMGh579Pwgmr9P3k,9551
|
|
92
|
+
fastmcp/server/auth/providers/discord.py,sha256=AK7WRydWNnXAUWnFeYUqgcdqsGkwFfcKZcyZMuOQcUw,12616
|
|
92
93
|
fastmcp/server/auth/providers/github.py,sha256=xsv-Qj1VJRc64YcRuUG4a61xFH1nqqVX_biC7B1su9U,12414
|
|
93
|
-
fastmcp/server/auth/providers/google.py,sha256=
|
|
94
|
+
fastmcp/server/auth/providers/google.py,sha256=BAw3XfB8fE2zr80OZUP-bZBnlHRmZQGuvmoVFgW5D1E,14723
|
|
94
95
|
fastmcp/server/auth/providers/in_memory.py,sha256=y8a8sfLQXctSB78yGpR3ZyG9x5pWRvI_t0LHgSZ4nvI,15444
|
|
95
96
|
fastmcp/server/auth/providers/introspection.py,sha256=v2hlcuxxwug5myCr4KcTZlawwazAWYVHuRb0d3er13w,10733
|
|
96
97
|
fastmcp/server/auth/providers/jwt.py,sha256=c-2Wji-CvuYt3U3unxjJR-5-EABRDks_755EpxKBDH8,20798
|
|
97
98
|
fastmcp/server/auth/providers/oci.py,sha256=-XXDCmxnyBYJ9kdv_Y3iLJ4MxLSOgUjZdJrGwH3vPrE,9849
|
|
98
|
-
fastmcp/server/auth/providers/scalekit.py,sha256=
|
|
99
|
+
fastmcp/server/auth/providers/scalekit.py,sha256=30J2HImUAkyknMgH7lUGytcDOy4d01ClxTrBCO4E3GQ,9064
|
|
99
100
|
fastmcp/server/auth/providers/supabase.py,sha256=9aK9fZ2OtccOF-ittMJnwj6sEzUNUTIrRPWAPLMwCac,7321
|
|
100
101
|
fastmcp/server/auth/providers/workos.py,sha256=_KWsgKPV4OJ6a37FaVgq2LIzM3Nx26G5QQhgS8x2MO4,17244
|
|
101
102
|
fastmcp/server/middleware/__init__.py,sha256=LXT2IcZI4gbAtR4TnA7v_1lOWBR6eaHiE3Cp32Pv0bc,155
|
|
@@ -108,9 +109,9 @@ fastmcp/server/middleware/timing.py,sha256=lL_xc-ErLD5lplfvd5-HIyWEbZhgNBYkcQ74K
|
|
|
108
109
|
fastmcp/server/middleware/tool_injection.py,sha256=zElqBN-yjZvcTADp57e0dn86kpxT9xsFqvYztiXuA08,3595
|
|
109
110
|
fastmcp/server/sampling/handler.py,sha256=yjLzvxlGllE-EY4bc6djsijEmwMT24PCpV6vJl-sPcI,580
|
|
110
111
|
fastmcp/tools/__init__.py,sha256=XGcaMkBgwr-AHzbNjyjdb3ATgp5TQ0wzSq0nsrBD__E,201
|
|
111
|
-
fastmcp/tools/tool.py,sha256=
|
|
112
|
+
fastmcp/tools/tool.py,sha256=stqAmfmtCLor_WCD9WpPE-a4dzvZX3wRP5imG97UvUU,22091
|
|
112
113
|
fastmcp/tools/tool_manager.py,sha256=pCQGvKimXYEigcUqRHBd6_mbfJwD2KN3i0SmFj9Fj_c,5913
|
|
113
|
-
fastmcp/tools/tool_transform.py,sha256=
|
|
114
|
+
fastmcp/tools/tool_transform.py,sha256=5ayCeLSUUTfhrWozODQ4yhF35t-XRnWPfc6vL3xtfJE,38579
|
|
114
115
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
115
116
|
fastmcp/utilities/auth.py,sha256=ZVHkNb4YBpLE1EmmFyhvFB2qfWDZdEYNH9TRI9jylOE,1140
|
|
116
117
|
fastmcp/utilities/cli.py,sha256=46gyOddE8kWhUV2lHFM7kA2v0YNyzcajvIX3Db8gJXk,12174
|
|
@@ -124,8 +125,8 @@ fastmcp/utilities/logging.py,sha256=61wVk5yQ62km3K8kZtkKtT_3EN26VL85GYW0aMtnwKA,
|
|
|
124
125
|
fastmcp/utilities/mcp_config.py,sha256=qATTXMGiYET-7PflOixQOgiw3aOizX-RlloRjAo7nwI,1796
|
|
125
126
|
fastmcp/utilities/openapi.py,sha256=Q3DD3Yc3JwoUG0usSI75OxbLT1ASq9FQwOymx1F8YZk,63339
|
|
126
127
|
fastmcp/utilities/tests.py,sha256=ChjKv-k5vf9y4ZHqItagBtooqPNrQiiJLAARUVOEP6M,8922
|
|
127
|
-
fastmcp/utilities/types.py,sha256=
|
|
128
|
-
fastmcp/utilities/ui.py,sha256=
|
|
128
|
+
fastmcp/utilities/types.py,sha256=GA6aweKOke8nJDnESka3EKv9vStfKK5NiCZwLUF8Ars,17231
|
|
129
|
+
fastmcp/utilities/ui.py,sha256=gcnha7Vj4xEBxdrS83EZlKpN_43AQzcgiZFEvkTqzqg,14252
|
|
129
130
|
fastmcp/utilities/mcp_server_config/__init__.py,sha256=hHBxEwRsrgN0Q-1bvj28X6UVGDpfG6dt3yfSBGsOY80,791
|
|
130
131
|
fastmcp/utilities/mcp_server_config/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
131
132
|
fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py,sha256=9XHryV-JnAcPP5YR_EzAkUDmj5Fm3PE8lNUgTflYZfs,15467
|
|
@@ -136,8 +137,8 @@ fastmcp/utilities/mcp_server_config/v1/environments/uv.py,sha256=DPVAXM5JDTN89wO
|
|
|
136
137
|
fastmcp/utilities/mcp_server_config/v1/sources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
137
138
|
fastmcp/utilities/mcp_server_config/v1/sources/base.py,sha256=Y5MCxJyoDsaxcBN1zDL0CZtF5oAXxT_yqQOI-ze9b34,967
|
|
138
139
|
fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py,sha256=eFX47XNXz2oKHW8MZvx60dqyHkBxdg2FMOrHcyAS28g,8106
|
|
139
|
-
fastmcp-2.13.
|
|
140
|
-
fastmcp-2.13.
|
|
141
|
-
fastmcp-2.13.
|
|
142
|
-
fastmcp-2.13.
|
|
143
|
-
fastmcp-2.13.
|
|
140
|
+
fastmcp-2.13.3.dist-info/METADATA,sha256=NJupIezScBX3GtYch_qp9e-VGA5k1w3pFvsgwoUnqf4,20512
|
|
141
|
+
fastmcp-2.13.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
142
|
+
fastmcp-2.13.3.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
143
|
+
fastmcp-2.13.3.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
144
|
+
fastmcp-2.13.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|