fastmcp 2.14.5__py3-none-any.whl → 3.0.0b1__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/_vendor/__init__.py +1 -0
- fastmcp/_vendor/docket_di/README.md +7 -0
- fastmcp/_vendor/docket_di/__init__.py +163 -0
- fastmcp/cli/cli.py +112 -28
- fastmcp/cli/install/claude_code.py +1 -5
- fastmcp/cli/install/claude_desktop.py +1 -5
- fastmcp/cli/install/cursor.py +1 -5
- fastmcp/cli/install/gemini_cli.py +1 -5
- fastmcp/cli/install/mcp_json.py +1 -6
- fastmcp/cli/run.py +146 -5
- fastmcp/client/__init__.py +7 -9
- fastmcp/client/auth/oauth.py +18 -17
- fastmcp/client/client.py +100 -870
- fastmcp/client/elicitation.py +1 -1
- fastmcp/client/mixins/__init__.py +13 -0
- fastmcp/client/mixins/prompts.py +295 -0
- fastmcp/client/mixins/resources.py +325 -0
- fastmcp/client/mixins/task_management.py +157 -0
- fastmcp/client/mixins/tools.py +397 -0
- fastmcp/client/sampling/handlers/anthropic.py +2 -2
- fastmcp/client/sampling/handlers/openai.py +1 -1
- fastmcp/client/tasks.py +3 -3
- fastmcp/client/telemetry.py +47 -0
- fastmcp/client/transports/__init__.py +38 -0
- fastmcp/client/transports/base.py +82 -0
- fastmcp/client/transports/config.py +170 -0
- fastmcp/client/transports/http.py +145 -0
- fastmcp/client/transports/inference.py +154 -0
- fastmcp/client/transports/memory.py +90 -0
- fastmcp/client/transports/sse.py +89 -0
- fastmcp/client/transports/stdio.py +543 -0
- fastmcp/contrib/component_manager/README.md +4 -10
- fastmcp/contrib/component_manager/__init__.py +1 -2
- fastmcp/contrib/component_manager/component_manager.py +95 -160
- fastmcp/contrib/component_manager/example.py +1 -1
- fastmcp/contrib/mcp_mixin/example.py +4 -4
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
- fastmcp/decorators.py +41 -0
- fastmcp/dependencies.py +12 -1
- fastmcp/exceptions.py +4 -0
- fastmcp/experimental/server/openapi/__init__.py +18 -15
- fastmcp/mcp_config.py +13 -4
- fastmcp/prompts/__init__.py +6 -3
- fastmcp/prompts/function_prompt.py +465 -0
- fastmcp/prompts/prompt.py +321 -271
- fastmcp/resources/__init__.py +5 -3
- fastmcp/resources/function_resource.py +335 -0
- fastmcp/resources/resource.py +325 -115
- fastmcp/resources/template.py +215 -43
- fastmcp/resources/types.py +27 -12
- fastmcp/server/__init__.py +2 -2
- fastmcp/server/auth/__init__.py +14 -0
- fastmcp/server/auth/auth.py +30 -10
- fastmcp/server/auth/authorization.py +190 -0
- fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
- fastmcp/server/auth/oauth_proxy/consent.py +361 -0
- fastmcp/server/auth/oauth_proxy/models.py +178 -0
- fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
- fastmcp/server/auth/oauth_proxy/ui.py +277 -0
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/auth0.py +24 -94
- fastmcp/server/auth/providers/aws.py +26 -95
- fastmcp/server/auth/providers/azure.py +41 -129
- fastmcp/server/auth/providers/descope.py +18 -49
- fastmcp/server/auth/providers/discord.py +25 -86
- fastmcp/server/auth/providers/github.py +23 -87
- fastmcp/server/auth/providers/google.py +24 -87
- fastmcp/server/auth/providers/introspection.py +60 -79
- fastmcp/server/auth/providers/jwt.py +30 -67
- fastmcp/server/auth/providers/oci.py +47 -110
- fastmcp/server/auth/providers/scalekit.py +23 -61
- fastmcp/server/auth/providers/supabase.py +18 -47
- fastmcp/server/auth/providers/workos.py +34 -127
- fastmcp/server/context.py +372 -419
- fastmcp/server/dependencies.py +541 -251
- fastmcp/server/elicitation.py +20 -18
- fastmcp/server/event_store.py +3 -3
- fastmcp/server/http.py +16 -6
- fastmcp/server/lifespan.py +198 -0
- fastmcp/server/low_level.py +92 -2
- fastmcp/server/middleware/__init__.py +5 -1
- fastmcp/server/middleware/authorization.py +312 -0
- fastmcp/server/middleware/caching.py +101 -54
- fastmcp/server/middleware/middleware.py +6 -9
- fastmcp/server/middleware/ping.py +70 -0
- fastmcp/server/middleware/tool_injection.py +2 -2
- fastmcp/server/mixins/__init__.py +7 -0
- fastmcp/server/mixins/lifespan.py +217 -0
- fastmcp/server/mixins/mcp_operations.py +392 -0
- fastmcp/server/mixins/transport.py +342 -0
- fastmcp/server/openapi/__init__.py +41 -21
- fastmcp/server/openapi/components.py +16 -339
- fastmcp/server/openapi/routing.py +34 -118
- fastmcp/server/openapi/server.py +67 -392
- fastmcp/server/providers/__init__.py +71 -0
- fastmcp/server/providers/aggregate.py +261 -0
- fastmcp/server/providers/base.py +578 -0
- fastmcp/server/providers/fastmcp_provider.py +674 -0
- fastmcp/server/providers/filesystem.py +226 -0
- fastmcp/server/providers/filesystem_discovery.py +327 -0
- fastmcp/server/providers/local_provider/__init__.py +11 -0
- fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
- fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
- fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
- fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
- fastmcp/server/providers/local_provider/local_provider.py +465 -0
- fastmcp/server/providers/openapi/__init__.py +39 -0
- fastmcp/server/providers/openapi/components.py +332 -0
- fastmcp/server/providers/openapi/provider.py +405 -0
- fastmcp/server/providers/openapi/routing.py +109 -0
- fastmcp/server/providers/proxy.py +867 -0
- fastmcp/server/providers/skills/__init__.py +59 -0
- fastmcp/server/providers/skills/_common.py +101 -0
- fastmcp/server/providers/skills/claude_provider.py +44 -0
- fastmcp/server/providers/skills/directory_provider.py +153 -0
- fastmcp/server/providers/skills/skill_provider.py +432 -0
- fastmcp/server/providers/skills/vendor_providers.py +142 -0
- fastmcp/server/providers/wrapped_provider.py +140 -0
- fastmcp/server/proxy.py +34 -700
- fastmcp/server/sampling/run.py +341 -2
- fastmcp/server/sampling/sampling_tool.py +4 -3
- fastmcp/server/server.py +1214 -2171
- fastmcp/server/tasks/__init__.py +2 -1
- fastmcp/server/tasks/capabilities.py +13 -1
- fastmcp/server/tasks/config.py +66 -3
- fastmcp/server/tasks/handlers.py +65 -273
- fastmcp/server/tasks/keys.py +4 -6
- fastmcp/server/tasks/requests.py +474 -0
- fastmcp/server/tasks/routing.py +76 -0
- fastmcp/server/tasks/subscriptions.py +20 -11
- fastmcp/server/telemetry.py +131 -0
- fastmcp/server/transforms/__init__.py +244 -0
- fastmcp/server/transforms/namespace.py +193 -0
- fastmcp/server/transforms/prompts_as_tools.py +175 -0
- fastmcp/server/transforms/resources_as_tools.py +190 -0
- fastmcp/server/transforms/tool_transform.py +96 -0
- fastmcp/server/transforms/version_filter.py +124 -0
- fastmcp/server/transforms/visibility.py +526 -0
- fastmcp/settings.py +34 -96
- fastmcp/telemetry.py +122 -0
- fastmcp/tools/__init__.py +10 -3
- fastmcp/tools/function_parsing.py +201 -0
- fastmcp/tools/function_tool.py +467 -0
- fastmcp/tools/tool.py +215 -362
- fastmcp/tools/tool_transform.py +38 -21
- fastmcp/utilities/async_utils.py +69 -0
- fastmcp/utilities/components.py +152 -91
- fastmcp/utilities/inspect.py +8 -20
- fastmcp/utilities/json_schema.py +12 -5
- fastmcp/utilities/json_schema_type.py +17 -15
- fastmcp/utilities/lifespan.py +56 -0
- fastmcp/utilities/logging.py +12 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/openapi/parser.py +3 -3
- fastmcp/utilities/pagination.py +80 -0
- fastmcp/utilities/skills.py +253 -0
- fastmcp/utilities/tests.py +0 -16
- fastmcp/utilities/timeout.py +47 -0
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/versions.py +285 -0
- {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
- fastmcp-3.0.0b1.dist-info/RECORD +228 -0
- fastmcp/client/transports.py +0 -1170
- fastmcp/contrib/component_manager/component_service.py +0 -209
- fastmcp/prompts/prompt_manager.py +0 -117
- fastmcp/resources/resource_manager.py +0 -338
- fastmcp/server/tasks/converters.py +0 -206
- fastmcp/server/tasks/protocol.py +0 -359
- fastmcp/tools/tool_manager.py +0 -170
- fastmcp/utilities/mcp_config.py +0 -56
- fastmcp-2.14.5.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"""Visibility transform for marking component visibility state.
|
|
2
|
+
|
|
3
|
+
Each Visibility instance marks components via internal metadata. Multiple
|
|
4
|
+
visibility transforms can be stacked - later transforms override earlier ones.
|
|
5
|
+
Final filtering happens at the Provider level.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
12
|
+
|
|
13
|
+
import mcp.types
|
|
14
|
+
|
|
15
|
+
from fastmcp.resources.resource import Resource
|
|
16
|
+
from fastmcp.resources.template import ResourceTemplate
|
|
17
|
+
from fastmcp.server.transforms import (
|
|
18
|
+
GetPromptNext,
|
|
19
|
+
GetResourceNext,
|
|
20
|
+
GetResourceTemplateNext,
|
|
21
|
+
GetToolNext,
|
|
22
|
+
Transform,
|
|
23
|
+
)
|
|
24
|
+
from fastmcp.utilities.versions import VersionSpec
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from fastmcp.prompts.prompt import Prompt
|
|
28
|
+
from fastmcp.server.context import Context
|
|
29
|
+
from fastmcp.tools.tool import Tool
|
|
30
|
+
from fastmcp.utilities.components import FastMCPComponent
|
|
31
|
+
|
|
32
|
+
T = TypeVar("T", bound="FastMCPComponent")
|
|
33
|
+
|
|
34
|
+
# Visibility state stored at meta["fastmcp"]["_internal"]["visibility"]
|
|
35
|
+
_FASTMCP_KEY = "fastmcp"
|
|
36
|
+
_INTERNAL_KEY = "_internal"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Visibility(Transform):
|
|
40
|
+
"""Sets visibility state on matching components.
|
|
41
|
+
|
|
42
|
+
Does NOT filter inline - just marks components with visibility state.
|
|
43
|
+
Later transforms in the chain can override earlier marks.
|
|
44
|
+
Final filtering happens at the Provider level after all transforms run.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
```python
|
|
48
|
+
# Disable components tagged "internal"
|
|
49
|
+
Visibility(False, tags={"internal"})
|
|
50
|
+
|
|
51
|
+
# Re-enable specific tool (override earlier disable)
|
|
52
|
+
Visibility(True, names={"safe_tool"})
|
|
53
|
+
|
|
54
|
+
# Allowlist via composition:
|
|
55
|
+
Visibility(False, match_all=True) # disable everything
|
|
56
|
+
Visibility(True, tags={"public"}) # enable public
|
|
57
|
+
```
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
enabled: bool,
|
|
63
|
+
*,
|
|
64
|
+
names: set[str] | None = None,
|
|
65
|
+
keys: set[str] | None = None,
|
|
66
|
+
version: VersionSpec | None = None,
|
|
67
|
+
tags: set[str] | None = None,
|
|
68
|
+
components: set[Literal["tool", "resource", "template", "prompt"]]
|
|
69
|
+
| None = None,
|
|
70
|
+
match_all: bool = False,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Initialize a visibility marker.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
enabled: If True, mark matching as enabled; if False, mark as disabled.
|
|
76
|
+
names: Component names or URIs to match.
|
|
77
|
+
keys: Component keys to match (e.g., {"tool:my_tool@v1"}).
|
|
78
|
+
version: Component version spec to match. Unversioned components (version=None)
|
|
79
|
+
will NOT match a version spec.
|
|
80
|
+
tags: Tags to match (component must have at least one).
|
|
81
|
+
components: Component types to match (e.g., {"tool", "prompt"}).
|
|
82
|
+
match_all: If True, matches all components regardless of other criteria.
|
|
83
|
+
"""
|
|
84
|
+
self._enabled = enabled
|
|
85
|
+
self.names = names
|
|
86
|
+
self.keys = keys
|
|
87
|
+
self.version = version
|
|
88
|
+
self.tags = tags # e.g., {"internal", "deprecated"}
|
|
89
|
+
self.components = components # e.g., {"tool", "prompt"}
|
|
90
|
+
self.match_all = match_all
|
|
91
|
+
|
|
92
|
+
def __repr__(self) -> str:
|
|
93
|
+
action = "enable" if self._enabled else "disable"
|
|
94
|
+
if self.match_all:
|
|
95
|
+
return f"Visibility({self._enabled}, match_all=True)"
|
|
96
|
+
parts = []
|
|
97
|
+
if self.names:
|
|
98
|
+
parts.append(f"names={set(self.names)}")
|
|
99
|
+
if self.keys:
|
|
100
|
+
parts.append(f"keys={set(self.keys)}")
|
|
101
|
+
if self.version:
|
|
102
|
+
parts.append(f"version={self.version!r}")
|
|
103
|
+
if self.components:
|
|
104
|
+
parts.append(f"components={set(self.components)}")
|
|
105
|
+
if self.tags:
|
|
106
|
+
parts.append(f"tags={set(self.tags)}")
|
|
107
|
+
if parts:
|
|
108
|
+
return f"Visibility({action}, {', '.join(parts)})"
|
|
109
|
+
return f"Visibility({action})"
|
|
110
|
+
|
|
111
|
+
def _matches(self, component: FastMCPComponent) -> bool:
|
|
112
|
+
"""Check if this transform applies to the component.
|
|
113
|
+
|
|
114
|
+
All specified criteria must match (intersection semantics).
|
|
115
|
+
An empty rule (no criteria) matches nothing.
|
|
116
|
+
Use match_all=True to match everything.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
component: Component to check.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if this transform should mark the component.
|
|
123
|
+
"""
|
|
124
|
+
# Match-all flag matches everything
|
|
125
|
+
if self.match_all:
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
# Empty criteria matches nothing (safe default)
|
|
129
|
+
if (
|
|
130
|
+
self.names is None
|
|
131
|
+
and self.keys is None
|
|
132
|
+
and self.version is None
|
|
133
|
+
and self.components is None
|
|
134
|
+
and self.tags is None
|
|
135
|
+
):
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
# Check component type if specified
|
|
139
|
+
if self.components is not None:
|
|
140
|
+
component_type = component.key.split(":")[
|
|
141
|
+
0
|
|
142
|
+
] # e.g., "tool" from "tool:foo@"
|
|
143
|
+
if component_type not in self.components:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
# Check keys if specified (exact match only)
|
|
147
|
+
if self.keys is not None:
|
|
148
|
+
if component.key not in self.keys:
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
# Check names if specified
|
|
152
|
+
if self.names is not None:
|
|
153
|
+
# For resources, also check URI; for templates, check uri_template
|
|
154
|
+
matches_name = component.name in self.names
|
|
155
|
+
matches_uri = False
|
|
156
|
+
if isinstance(component, Resource):
|
|
157
|
+
matches_uri = str(component.uri) in self.names
|
|
158
|
+
elif isinstance(component, ResourceTemplate):
|
|
159
|
+
matches_uri = component.uri_template in self.names
|
|
160
|
+
if not (matches_name or matches_uri):
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
# Check version if specified
|
|
164
|
+
# Note: match_none=False means unversioned components don't match a version spec
|
|
165
|
+
if self.version is not None and not self.version.matches(
|
|
166
|
+
component.version, match_none=False
|
|
167
|
+
):
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
# Check tags if specified (component must have at least one matching tag)
|
|
171
|
+
return self.tags is None or bool(component.tags & self.tags)
|
|
172
|
+
|
|
173
|
+
def _mark_component(self, component: T) -> T:
|
|
174
|
+
"""Set visibility state in component metadata if rule matches."""
|
|
175
|
+
if not self._matches(component):
|
|
176
|
+
return component
|
|
177
|
+
|
|
178
|
+
# Create new dicts to avoid mutating shared dicts
|
|
179
|
+
# (e.g., when Tool.from_tool shares the meta dict between tools)
|
|
180
|
+
if component.meta is None:
|
|
181
|
+
component.meta = {
|
|
182
|
+
_FASTMCP_KEY: {_INTERNAL_KEY: {"visibility": self._enabled}}
|
|
183
|
+
}
|
|
184
|
+
else:
|
|
185
|
+
old_fastmcp = component.meta.get(_FASTMCP_KEY, {})
|
|
186
|
+
old_internal = old_fastmcp.get(_INTERNAL_KEY, {})
|
|
187
|
+
new_internal = {**old_internal, "visibility": self._enabled}
|
|
188
|
+
new_fastmcp = {**old_fastmcp, _INTERNAL_KEY: new_internal}
|
|
189
|
+
component.meta = {**component.meta, _FASTMCP_KEY: new_fastmcp}
|
|
190
|
+
return component
|
|
191
|
+
|
|
192
|
+
# -------------------------------------------------------------------------
|
|
193
|
+
# Transform methods (mark components, don't filter)
|
|
194
|
+
# -------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
|
|
197
|
+
"""Mark tools by visibility state."""
|
|
198
|
+
return [self._mark_component(t) for t in tools]
|
|
199
|
+
|
|
200
|
+
async def get_tool(
|
|
201
|
+
self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
|
|
202
|
+
) -> Tool | None:
|
|
203
|
+
"""Mark tool if found."""
|
|
204
|
+
tool = await call_next(name, version=version)
|
|
205
|
+
if tool is None:
|
|
206
|
+
return None
|
|
207
|
+
return self._mark_component(tool)
|
|
208
|
+
|
|
209
|
+
# -------------------------------------------------------------------------
|
|
210
|
+
# Resources
|
|
211
|
+
# -------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
async def list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]:
|
|
214
|
+
"""Mark resources by visibility state."""
|
|
215
|
+
return [self._mark_component(r) for r in resources]
|
|
216
|
+
|
|
217
|
+
async def get_resource(
|
|
218
|
+
self,
|
|
219
|
+
uri: str,
|
|
220
|
+
call_next: GetResourceNext,
|
|
221
|
+
*,
|
|
222
|
+
version: VersionSpec | None = None,
|
|
223
|
+
) -> Resource | None:
|
|
224
|
+
"""Mark resource if found."""
|
|
225
|
+
resource = await call_next(uri, version=version)
|
|
226
|
+
if resource is None:
|
|
227
|
+
return None
|
|
228
|
+
return self._mark_component(resource)
|
|
229
|
+
|
|
230
|
+
# -------------------------------------------------------------------------
|
|
231
|
+
# Resource Templates
|
|
232
|
+
# -------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
async def list_resource_templates(
|
|
235
|
+
self, templates: Sequence[ResourceTemplate]
|
|
236
|
+
) -> Sequence[ResourceTemplate]:
|
|
237
|
+
"""Mark resource templates by visibility state."""
|
|
238
|
+
return [self._mark_component(t) for t in templates]
|
|
239
|
+
|
|
240
|
+
async def get_resource_template(
|
|
241
|
+
self,
|
|
242
|
+
uri: str,
|
|
243
|
+
call_next: GetResourceTemplateNext,
|
|
244
|
+
*,
|
|
245
|
+
version: VersionSpec | None = None,
|
|
246
|
+
) -> ResourceTemplate | None:
|
|
247
|
+
"""Mark resource template if found."""
|
|
248
|
+
template = await call_next(uri, version=version)
|
|
249
|
+
if template is None:
|
|
250
|
+
return None
|
|
251
|
+
return self._mark_component(template)
|
|
252
|
+
|
|
253
|
+
# -------------------------------------------------------------------------
|
|
254
|
+
# Prompts
|
|
255
|
+
# -------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
async def list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]:
|
|
258
|
+
"""Mark prompts by visibility state."""
|
|
259
|
+
return [self._mark_component(p) for p in prompts]
|
|
260
|
+
|
|
261
|
+
async def get_prompt(
|
|
262
|
+
self, name: str, call_next: GetPromptNext, *, version: VersionSpec | None = None
|
|
263
|
+
) -> Prompt | None:
|
|
264
|
+
"""Mark prompt if found."""
|
|
265
|
+
prompt = await call_next(name, version=version)
|
|
266
|
+
if prompt is None:
|
|
267
|
+
return None
|
|
268
|
+
return self._mark_component(prompt)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def is_enabled(component: FastMCPComponent) -> bool:
|
|
272
|
+
"""Check if component is enabled.
|
|
273
|
+
|
|
274
|
+
Returns True if:
|
|
275
|
+
- No visibility mark exists (default is enabled)
|
|
276
|
+
- Visibility mark is True
|
|
277
|
+
|
|
278
|
+
Returns False if visibility mark is False.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
component: Component to check.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
True if component should be enabled/visible to clients.
|
|
285
|
+
"""
|
|
286
|
+
meta = component.meta or {}
|
|
287
|
+
fastmcp = meta.get(_FASTMCP_KEY, {})
|
|
288
|
+
internal = fastmcp.get(_INTERNAL_KEY, {})
|
|
289
|
+
return internal.get("visibility", True) # Default True if not set
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# -------------------------------------------------------------------------
|
|
293
|
+
# Session visibility control
|
|
294
|
+
# -------------------------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
if TYPE_CHECKING:
|
|
297
|
+
from fastmcp.server.context import Context
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
async def get_visibility_rules(context: Context) -> list[dict[str, Any]]:
|
|
301
|
+
"""Load visibility rule dicts from session state."""
|
|
302
|
+
return await context.get_state("_visibility_rules") or []
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
async def save_visibility_rules(
|
|
306
|
+
context: Context,
|
|
307
|
+
rules: list[dict[str, Any]],
|
|
308
|
+
*,
|
|
309
|
+
components: set[Literal["tool", "resource", "template", "prompt"]] | None = None,
|
|
310
|
+
) -> None:
|
|
311
|
+
"""Save visibility rule dicts to session state and send notifications.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
context: The context to save rules for.
|
|
315
|
+
rules: The visibility rules to save.
|
|
316
|
+
components: Optional hint about which component types are affected.
|
|
317
|
+
If None, sends notifications for all types (safe default).
|
|
318
|
+
If provided, only sends notifications for specified types.
|
|
319
|
+
"""
|
|
320
|
+
await context.set_state("_visibility_rules", rules)
|
|
321
|
+
|
|
322
|
+
# Send notifications based on components hint
|
|
323
|
+
# Note: MCP has no separate template notification - templates use ResourceListChangedNotification
|
|
324
|
+
if components is None or "tool" in components:
|
|
325
|
+
await context.send_notification(mcp.types.ToolListChangedNotification())
|
|
326
|
+
if components is None or "resource" in components or "template" in components:
|
|
327
|
+
await context.send_notification(mcp.types.ResourceListChangedNotification())
|
|
328
|
+
if components is None or "prompt" in components:
|
|
329
|
+
await context.send_notification(mcp.types.PromptListChangedNotification())
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def create_visibility_transforms(rules: list[dict[str, Any]]) -> list[Visibility]:
|
|
333
|
+
"""Convert rule dicts to Visibility transforms."""
|
|
334
|
+
transforms = []
|
|
335
|
+
for params in rules:
|
|
336
|
+
version = None
|
|
337
|
+
if params.get("version"):
|
|
338
|
+
version_dict = params["version"]
|
|
339
|
+
version = VersionSpec(
|
|
340
|
+
gte=version_dict.get("gte"),
|
|
341
|
+
lt=version_dict.get("lt"),
|
|
342
|
+
eq=version_dict.get("eq"),
|
|
343
|
+
)
|
|
344
|
+
transforms.append(
|
|
345
|
+
Visibility(
|
|
346
|
+
params["enabled"],
|
|
347
|
+
names=set(params["names"]) if params.get("names") else None,
|
|
348
|
+
keys=set(params["keys"]) if params.get("keys") else None,
|
|
349
|
+
version=version,
|
|
350
|
+
tags=set(params["tags"]) if params.get("tags") else None,
|
|
351
|
+
components=(
|
|
352
|
+
set(params["components"]) if params.get("components") else None
|
|
353
|
+
),
|
|
354
|
+
match_all=params.get("match_all", False),
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
return transforms
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
async def get_session_transforms(context: Context) -> list[Visibility]:
|
|
361
|
+
"""Get session-specific Visibility transforms from state store."""
|
|
362
|
+
try:
|
|
363
|
+
# Will raise RuntimeError if no session available
|
|
364
|
+
_ = context.session_id
|
|
365
|
+
except RuntimeError:
|
|
366
|
+
return []
|
|
367
|
+
|
|
368
|
+
rules = await get_visibility_rules(context)
|
|
369
|
+
return create_visibility_transforms(rules)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
async def enable_components(
|
|
373
|
+
context: Context,
|
|
374
|
+
*,
|
|
375
|
+
names: set[str] | None = None,
|
|
376
|
+
keys: set[str] | None = None,
|
|
377
|
+
version: VersionSpec | None = None,
|
|
378
|
+
tags: set[str] | None = None,
|
|
379
|
+
components: set[Literal["tool", "resource", "template", "prompt"]] | None = None,
|
|
380
|
+
match_all: bool = False,
|
|
381
|
+
) -> None:
|
|
382
|
+
"""Enable components matching criteria for this session only.
|
|
383
|
+
|
|
384
|
+
Session rules override global transforms. Rules accumulate - each call
|
|
385
|
+
adds a new rule to the session. Later marks override earlier ones
|
|
386
|
+
(Visibility transform semantics).
|
|
387
|
+
|
|
388
|
+
Sends notifications to this session only: ToolListChangedNotification,
|
|
389
|
+
ResourceListChangedNotification, and PromptListChangedNotification.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
context: The context for this session.
|
|
393
|
+
names: Component names or URIs to match.
|
|
394
|
+
keys: Component keys to match (e.g., {"tool:my_tool@v1"}).
|
|
395
|
+
version: Component version spec to match.
|
|
396
|
+
tags: Tags to match (component must have at least one).
|
|
397
|
+
components: Component types to match (e.g., {"tool", "prompt"}).
|
|
398
|
+
match_all: If True, matches all components regardless of other criteria.
|
|
399
|
+
"""
|
|
400
|
+
# Normalize empty sets to None (empty = match all)
|
|
401
|
+
components = components if components else None
|
|
402
|
+
|
|
403
|
+
# Load current rules
|
|
404
|
+
rules = await get_visibility_rules(context)
|
|
405
|
+
|
|
406
|
+
# Create new rule dict
|
|
407
|
+
rule: dict[str, Any] = {
|
|
408
|
+
"enabled": True,
|
|
409
|
+
"names": list(names) if names else None,
|
|
410
|
+
"keys": list(keys) if keys else None,
|
|
411
|
+
"version": (
|
|
412
|
+
{"gte": version.gte, "lt": version.lt, "eq": version.eq}
|
|
413
|
+
if version
|
|
414
|
+
else None
|
|
415
|
+
),
|
|
416
|
+
"tags": list(tags) if tags else None,
|
|
417
|
+
"components": list(components) if components else None,
|
|
418
|
+
"match_all": match_all,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# Add and save (notifications sent by save_visibility_rules)
|
|
422
|
+
rules.append(rule)
|
|
423
|
+
await save_visibility_rules(context, rules, components=components)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
async def disable_components(
|
|
427
|
+
context: Context,
|
|
428
|
+
*,
|
|
429
|
+
names: set[str] | None = None,
|
|
430
|
+
keys: set[str] | None = None,
|
|
431
|
+
version: VersionSpec | None = None,
|
|
432
|
+
tags: set[str] | None = None,
|
|
433
|
+
components: set[Literal["tool", "resource", "template", "prompt"]] | None = None,
|
|
434
|
+
match_all: bool = False,
|
|
435
|
+
) -> None:
|
|
436
|
+
"""Disable components matching criteria for this session only.
|
|
437
|
+
|
|
438
|
+
Session rules override global transforms. Rules accumulate - each call
|
|
439
|
+
adds a new rule to the session. Later marks override earlier ones
|
|
440
|
+
(Visibility transform semantics).
|
|
441
|
+
|
|
442
|
+
Sends notifications to this session only: ToolListChangedNotification,
|
|
443
|
+
ResourceListChangedNotification, and PromptListChangedNotification.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
context: The context for this session.
|
|
447
|
+
names: Component names or URIs to match.
|
|
448
|
+
keys: Component keys to match (e.g., {"tool:my_tool@v1"}).
|
|
449
|
+
version: Component version spec to match.
|
|
450
|
+
tags: Tags to match (component must have at least one).
|
|
451
|
+
components: Component types to match (e.g., {"tool", "prompt"}).
|
|
452
|
+
match_all: If True, matches all components regardless of other criteria.
|
|
453
|
+
"""
|
|
454
|
+
# Normalize empty sets to None (empty = match all)
|
|
455
|
+
components = components if components else None
|
|
456
|
+
|
|
457
|
+
# Load current rules
|
|
458
|
+
rules = await get_visibility_rules(context)
|
|
459
|
+
|
|
460
|
+
# Create new rule dict
|
|
461
|
+
rule: dict[str, Any] = {
|
|
462
|
+
"enabled": False,
|
|
463
|
+
"names": list(names) if names else None,
|
|
464
|
+
"keys": list(keys) if keys else None,
|
|
465
|
+
"version": (
|
|
466
|
+
{"gte": version.gte, "lt": version.lt, "eq": version.eq}
|
|
467
|
+
if version
|
|
468
|
+
else None
|
|
469
|
+
),
|
|
470
|
+
"tags": list(tags) if tags else None,
|
|
471
|
+
"components": list(components) if components else None,
|
|
472
|
+
"match_all": match_all,
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
# Add and save (notifications sent by save_visibility_rules)
|
|
476
|
+
rules.append(rule)
|
|
477
|
+
await save_visibility_rules(context, rules, components=components)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
async def reset_visibility(context: Context) -> None:
|
|
481
|
+
"""Clear all session visibility rules.
|
|
482
|
+
|
|
483
|
+
Use this to reset session visibility back to global defaults.
|
|
484
|
+
|
|
485
|
+
Sends notifications to this session only: ToolListChangedNotification,
|
|
486
|
+
ResourceListChangedNotification, and PromptListChangedNotification.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
context: The context for this session.
|
|
490
|
+
"""
|
|
491
|
+
await save_visibility_rules(context, [])
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
ComponentT = TypeVar("ComponentT", bound="FastMCPComponent")
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
async def apply_session_transforms(
|
|
498
|
+
components: Sequence[ComponentT],
|
|
499
|
+
) -> Sequence[ComponentT]:
|
|
500
|
+
"""Apply session-specific visibility transforms to components.
|
|
501
|
+
|
|
502
|
+
This helper applies session-level enable/disable rules by marking
|
|
503
|
+
components with their visibility state. Session transforms override
|
|
504
|
+
global transforms due to mark-based semantics (later marks win).
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
components: The components to apply session transforms to.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
The components with session transforms applied.
|
|
511
|
+
"""
|
|
512
|
+
from fastmcp.server.context import _current_context
|
|
513
|
+
|
|
514
|
+
current_ctx = _current_context.get()
|
|
515
|
+
if current_ctx is None:
|
|
516
|
+
return components
|
|
517
|
+
|
|
518
|
+
session_transforms = await get_session_transforms(current_ctx)
|
|
519
|
+
if not session_transforms:
|
|
520
|
+
return components
|
|
521
|
+
|
|
522
|
+
# Apply each transform's marking to each component
|
|
523
|
+
result = list(components)
|
|
524
|
+
for transform in session_transforms:
|
|
525
|
+
result = [transform._mark_component(c) for c in result]
|
|
526
|
+
return result
|
fastmcp/settings.py
CHANGED
|
@@ -5,10 +5,10 @@ import os
|
|
|
5
5
|
import warnings
|
|
6
6
|
from datetime import timedelta
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Annotated, Any, Literal
|
|
9
9
|
|
|
10
10
|
from platformdirs import user_data_dir
|
|
11
|
-
from pydantic import Field,
|
|
11
|
+
from pydantic import Field, field_validator
|
|
12
12
|
from pydantic_settings import (
|
|
13
13
|
BaseSettings,
|
|
14
14
|
SettingsConfigDict,
|
|
@@ -26,9 +26,6 @@ DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
|
|
|
26
26
|
|
|
27
27
|
TEN_MB_IN_BYTES = 1024 * 1024 * 10
|
|
28
28
|
|
|
29
|
-
if TYPE_CHECKING:
|
|
30
|
-
from fastmcp.server.auth.auth import AuthProvider
|
|
31
|
-
|
|
32
29
|
|
|
33
30
|
class DocketSettings(BaseSettings):
|
|
34
31
|
"""Docket worker configuration."""
|
|
@@ -198,6 +195,18 @@ class Settings(BaseSettings):
|
|
|
198
195
|
|
|
199
196
|
docket: DocketSettings = DocketSettings()
|
|
200
197
|
|
|
198
|
+
enable_rich_logging: Annotated[
|
|
199
|
+
bool,
|
|
200
|
+
Field(
|
|
201
|
+
description=inspect.cleandoc(
|
|
202
|
+
"""
|
|
203
|
+
If True, will use rich formatting for log output. If False,
|
|
204
|
+
will use standard Python logging without rich formatting.
|
|
205
|
+
"""
|
|
206
|
+
)
|
|
207
|
+
),
|
|
208
|
+
] = True
|
|
209
|
+
|
|
201
210
|
enable_rich_tracebacks: Annotated[
|
|
202
211
|
bool,
|
|
203
212
|
Field(
|
|
@@ -296,76 +305,6 @@ class Settings(BaseSettings):
|
|
|
296
305
|
False # If True, uses true stateless mode (new transport per request)
|
|
297
306
|
)
|
|
298
307
|
|
|
299
|
-
# Auth settings
|
|
300
|
-
server_auth: Annotated[
|
|
301
|
-
str | None,
|
|
302
|
-
Field(
|
|
303
|
-
description=inspect.cleandoc(
|
|
304
|
-
"""
|
|
305
|
-
Configure the authentication provider for the server by specifying
|
|
306
|
-
the full module path to an AuthProvider class (e.g.,
|
|
307
|
-
'fastmcp.server.auth.providers.google.GoogleProvider').
|
|
308
|
-
|
|
309
|
-
The specified class will be imported and instantiated automatically
|
|
310
|
-
during FastMCP server creation. Any class that inherits from AuthProvider
|
|
311
|
-
can be used, including custom implementations.
|
|
312
|
-
|
|
313
|
-
If None, no automatic configuration will take place.
|
|
314
|
-
|
|
315
|
-
This setting is *always* overridden by any auth provider passed to the
|
|
316
|
-
FastMCP constructor.
|
|
317
|
-
|
|
318
|
-
Note that most auth providers require additional configuration
|
|
319
|
-
that must be provided via env vars.
|
|
320
|
-
|
|
321
|
-
Examples:
|
|
322
|
-
- fastmcp.server.auth.providers.google.GoogleProvider
|
|
323
|
-
- fastmcp.server.auth.providers.jwt.JWTVerifier
|
|
324
|
-
- mycompany.auth.CustomAuthProvider
|
|
325
|
-
"""
|
|
326
|
-
),
|
|
327
|
-
),
|
|
328
|
-
] = None
|
|
329
|
-
|
|
330
|
-
include_tags: Annotated[
|
|
331
|
-
set[str] | None,
|
|
332
|
-
Field(
|
|
333
|
-
description=inspect.cleandoc(
|
|
334
|
-
"""
|
|
335
|
-
If provided, only components that match these tags will be
|
|
336
|
-
exposed to clients. A component is considered to match if ANY of
|
|
337
|
-
its tags match ANY of the tags in the set.
|
|
338
|
-
"""
|
|
339
|
-
),
|
|
340
|
-
),
|
|
341
|
-
] = None
|
|
342
|
-
exclude_tags: Annotated[
|
|
343
|
-
set[str] | None,
|
|
344
|
-
Field(
|
|
345
|
-
description=inspect.cleandoc(
|
|
346
|
-
"""
|
|
347
|
-
If provided, components that match these tags will be excluded
|
|
348
|
-
from the server. A component is considered to match if ANY of
|
|
349
|
-
its tags match ANY of the tags in the set.
|
|
350
|
-
"""
|
|
351
|
-
),
|
|
352
|
-
),
|
|
353
|
-
] = None
|
|
354
|
-
|
|
355
|
-
include_fastmcp_meta: Annotated[
|
|
356
|
-
bool,
|
|
357
|
-
Field(
|
|
358
|
-
description=inspect.cleandoc(
|
|
359
|
-
"""
|
|
360
|
-
Whether to include FastMCP meta in the server's MCP responses.
|
|
361
|
-
If True, a `_fastmcp` key will be added to the `meta` field of
|
|
362
|
-
all MCP component responses. This key will contain a dict of
|
|
363
|
-
various FastMCP-specific metadata, such as tags.
|
|
364
|
-
"""
|
|
365
|
-
),
|
|
366
|
-
),
|
|
367
|
-
] = True
|
|
368
|
-
|
|
369
308
|
mounted_components_raise_on_load_error: Annotated[
|
|
370
309
|
bool,
|
|
371
310
|
Field(
|
|
@@ -379,14 +318,15 @@ class Settings(BaseSettings):
|
|
|
379
318
|
),
|
|
380
319
|
] = False
|
|
381
320
|
|
|
382
|
-
|
|
321
|
+
show_server_banner: Annotated[
|
|
383
322
|
bool,
|
|
384
323
|
Field(
|
|
385
324
|
description=inspect.cleandoc(
|
|
386
325
|
"""
|
|
387
|
-
If True, the server banner will be displayed when running the server
|
|
388
|
-
This setting can be overridden by the --no-banner CLI flag
|
|
389
|
-
|
|
326
|
+
If True, the server banner will be displayed when running the server.
|
|
327
|
+
This setting can be overridden by the --no-banner CLI flag or by
|
|
328
|
+
passing show_banner=False to server.run().
|
|
329
|
+
Set to False via FASTMCP_SHOW_SERVER_BANNER=false to suppress the banner.
|
|
390
330
|
"""
|
|
391
331
|
),
|
|
392
332
|
),
|
|
@@ -407,21 +347,19 @@ class Settings(BaseSettings):
|
|
|
407
347
|
),
|
|
408
348
|
] = "stable"
|
|
409
349
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
# https://github.com/jlowin/fastmcp/issues/1749
|
|
418
|
-
# Pydantic imports the module in an ImportString during model validation, but we don't want the server
|
|
419
|
-
# auth module imported during settings creation as it imports dependencies we aren't ready for yet.
|
|
420
|
-
# To fix this while limiting breaking changes, we delay the import by only creating the ImportString
|
|
421
|
-
# when the class is actually needed
|
|
422
|
-
|
|
423
|
-
type_adapter = get_cached_typeadapter(ImportString)
|
|
424
|
-
|
|
425
|
-
auth_class = type_adapter.validate_python(self.server_auth)
|
|
350
|
+
decorator_mode: Annotated[
|
|
351
|
+
Literal["function", "object"],
|
|
352
|
+
Field(
|
|
353
|
+
description=inspect.cleandoc(
|
|
354
|
+
"""
|
|
355
|
+
Controls what decorators (@tool, @resource, @prompt) return.
|
|
426
356
|
|
|
427
|
-
|
|
357
|
+
- "function" (default): Decorators return the original function unchanged.
|
|
358
|
+
The function remains callable and is registered with the server normally.
|
|
359
|
+
- "object" (deprecated): Decorators return component objects (FunctionTool,
|
|
360
|
+
FunctionResource, FunctionPrompt). This was the default behavior in v2 and
|
|
361
|
+
will be removed in a future version.
|
|
362
|
+
"""
|
|
363
|
+
),
|
|
364
|
+
),
|
|
365
|
+
] = "function"
|