fastmcp 2.14.4__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.
Files changed (175) hide show
  1. fastmcp/_vendor/__init__.py +1 -0
  2. fastmcp/_vendor/docket_di/README.md +7 -0
  3. fastmcp/_vendor/docket_di/__init__.py +163 -0
  4. fastmcp/cli/cli.py +112 -28
  5. fastmcp/cli/install/claude_code.py +1 -5
  6. fastmcp/cli/install/claude_desktop.py +1 -5
  7. fastmcp/cli/install/cursor.py +1 -5
  8. fastmcp/cli/install/gemini_cli.py +1 -5
  9. fastmcp/cli/install/mcp_json.py +1 -6
  10. fastmcp/cli/run.py +146 -5
  11. fastmcp/client/__init__.py +7 -9
  12. fastmcp/client/auth/oauth.py +18 -17
  13. fastmcp/client/client.py +100 -870
  14. fastmcp/client/elicitation.py +1 -1
  15. fastmcp/client/mixins/__init__.py +13 -0
  16. fastmcp/client/mixins/prompts.py +295 -0
  17. fastmcp/client/mixins/resources.py +325 -0
  18. fastmcp/client/mixins/task_management.py +157 -0
  19. fastmcp/client/mixins/tools.py +397 -0
  20. fastmcp/client/sampling/handlers/anthropic.py +2 -2
  21. fastmcp/client/sampling/handlers/openai.py +1 -1
  22. fastmcp/client/tasks.py +3 -3
  23. fastmcp/client/telemetry.py +47 -0
  24. fastmcp/client/transports/__init__.py +38 -0
  25. fastmcp/client/transports/base.py +82 -0
  26. fastmcp/client/transports/config.py +170 -0
  27. fastmcp/client/transports/http.py +145 -0
  28. fastmcp/client/transports/inference.py +154 -0
  29. fastmcp/client/transports/memory.py +90 -0
  30. fastmcp/client/transports/sse.py +89 -0
  31. fastmcp/client/transports/stdio.py +543 -0
  32. fastmcp/contrib/component_manager/README.md +4 -10
  33. fastmcp/contrib/component_manager/__init__.py +1 -2
  34. fastmcp/contrib/component_manager/component_manager.py +95 -160
  35. fastmcp/contrib/component_manager/example.py +1 -1
  36. fastmcp/contrib/mcp_mixin/example.py +4 -4
  37. fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
  38. fastmcp/decorators.py +41 -0
  39. fastmcp/dependencies.py +12 -1
  40. fastmcp/exceptions.py +4 -0
  41. fastmcp/experimental/server/openapi/__init__.py +18 -15
  42. fastmcp/mcp_config.py +13 -4
  43. fastmcp/prompts/__init__.py +6 -3
  44. fastmcp/prompts/function_prompt.py +465 -0
  45. fastmcp/prompts/prompt.py +321 -271
  46. fastmcp/resources/__init__.py +5 -3
  47. fastmcp/resources/function_resource.py +335 -0
  48. fastmcp/resources/resource.py +325 -115
  49. fastmcp/resources/template.py +215 -43
  50. fastmcp/resources/types.py +27 -12
  51. fastmcp/server/__init__.py +2 -2
  52. fastmcp/server/auth/__init__.py +14 -0
  53. fastmcp/server/auth/auth.py +30 -10
  54. fastmcp/server/auth/authorization.py +190 -0
  55. fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
  56. fastmcp/server/auth/oauth_proxy/consent.py +361 -0
  57. fastmcp/server/auth/oauth_proxy/models.py +178 -0
  58. fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
  59. fastmcp/server/auth/oauth_proxy/ui.py +277 -0
  60. fastmcp/server/auth/oidc_proxy.py +2 -2
  61. fastmcp/server/auth/providers/auth0.py +24 -94
  62. fastmcp/server/auth/providers/aws.py +26 -95
  63. fastmcp/server/auth/providers/azure.py +41 -129
  64. fastmcp/server/auth/providers/descope.py +18 -49
  65. fastmcp/server/auth/providers/discord.py +25 -86
  66. fastmcp/server/auth/providers/github.py +23 -87
  67. fastmcp/server/auth/providers/google.py +24 -87
  68. fastmcp/server/auth/providers/introspection.py +60 -79
  69. fastmcp/server/auth/providers/jwt.py +30 -67
  70. fastmcp/server/auth/providers/oci.py +47 -110
  71. fastmcp/server/auth/providers/scalekit.py +23 -61
  72. fastmcp/server/auth/providers/supabase.py +18 -47
  73. fastmcp/server/auth/providers/workos.py +34 -127
  74. fastmcp/server/context.py +372 -419
  75. fastmcp/server/dependencies.py +541 -251
  76. fastmcp/server/elicitation.py +20 -18
  77. fastmcp/server/event_store.py +3 -3
  78. fastmcp/server/http.py +16 -6
  79. fastmcp/server/lifespan.py +198 -0
  80. fastmcp/server/low_level.py +92 -2
  81. fastmcp/server/middleware/__init__.py +5 -1
  82. fastmcp/server/middleware/authorization.py +312 -0
  83. fastmcp/server/middleware/caching.py +101 -54
  84. fastmcp/server/middleware/middleware.py +6 -9
  85. fastmcp/server/middleware/ping.py +70 -0
  86. fastmcp/server/middleware/tool_injection.py +2 -2
  87. fastmcp/server/mixins/__init__.py +7 -0
  88. fastmcp/server/mixins/lifespan.py +217 -0
  89. fastmcp/server/mixins/mcp_operations.py +392 -0
  90. fastmcp/server/mixins/transport.py +342 -0
  91. fastmcp/server/openapi/__init__.py +41 -21
  92. fastmcp/server/openapi/components.py +16 -339
  93. fastmcp/server/openapi/routing.py +34 -118
  94. fastmcp/server/openapi/server.py +67 -392
  95. fastmcp/server/providers/__init__.py +71 -0
  96. fastmcp/server/providers/aggregate.py +261 -0
  97. fastmcp/server/providers/base.py +578 -0
  98. fastmcp/server/providers/fastmcp_provider.py +674 -0
  99. fastmcp/server/providers/filesystem.py +226 -0
  100. fastmcp/server/providers/filesystem_discovery.py +327 -0
  101. fastmcp/server/providers/local_provider/__init__.py +11 -0
  102. fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
  103. fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
  104. fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
  105. fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
  106. fastmcp/server/providers/local_provider/local_provider.py +465 -0
  107. fastmcp/server/providers/openapi/__init__.py +39 -0
  108. fastmcp/server/providers/openapi/components.py +332 -0
  109. fastmcp/server/providers/openapi/provider.py +405 -0
  110. fastmcp/server/providers/openapi/routing.py +109 -0
  111. fastmcp/server/providers/proxy.py +867 -0
  112. fastmcp/server/providers/skills/__init__.py +59 -0
  113. fastmcp/server/providers/skills/_common.py +101 -0
  114. fastmcp/server/providers/skills/claude_provider.py +44 -0
  115. fastmcp/server/providers/skills/directory_provider.py +153 -0
  116. fastmcp/server/providers/skills/skill_provider.py +432 -0
  117. fastmcp/server/providers/skills/vendor_providers.py +142 -0
  118. fastmcp/server/providers/wrapped_provider.py +140 -0
  119. fastmcp/server/proxy.py +34 -700
  120. fastmcp/server/sampling/run.py +341 -2
  121. fastmcp/server/sampling/sampling_tool.py +4 -3
  122. fastmcp/server/server.py +1214 -2171
  123. fastmcp/server/tasks/__init__.py +2 -1
  124. fastmcp/server/tasks/capabilities.py +13 -1
  125. fastmcp/server/tasks/config.py +66 -3
  126. fastmcp/server/tasks/handlers.py +65 -273
  127. fastmcp/server/tasks/keys.py +4 -6
  128. fastmcp/server/tasks/requests.py +474 -0
  129. fastmcp/server/tasks/routing.py +76 -0
  130. fastmcp/server/tasks/subscriptions.py +20 -11
  131. fastmcp/server/telemetry.py +131 -0
  132. fastmcp/server/transforms/__init__.py +244 -0
  133. fastmcp/server/transforms/namespace.py +193 -0
  134. fastmcp/server/transforms/prompts_as_tools.py +175 -0
  135. fastmcp/server/transforms/resources_as_tools.py +190 -0
  136. fastmcp/server/transforms/tool_transform.py +96 -0
  137. fastmcp/server/transforms/version_filter.py +124 -0
  138. fastmcp/server/transforms/visibility.py +526 -0
  139. fastmcp/settings.py +34 -96
  140. fastmcp/telemetry.py +122 -0
  141. fastmcp/tools/__init__.py +10 -3
  142. fastmcp/tools/function_parsing.py +201 -0
  143. fastmcp/tools/function_tool.py +467 -0
  144. fastmcp/tools/tool.py +215 -362
  145. fastmcp/tools/tool_transform.py +38 -21
  146. fastmcp/utilities/async_utils.py +69 -0
  147. fastmcp/utilities/components.py +152 -91
  148. fastmcp/utilities/inspect.py +8 -20
  149. fastmcp/utilities/json_schema.py +12 -5
  150. fastmcp/utilities/json_schema_type.py +17 -15
  151. fastmcp/utilities/lifespan.py +56 -0
  152. fastmcp/utilities/logging.py +12 -4
  153. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  154. fastmcp/utilities/openapi/parser.py +3 -3
  155. fastmcp/utilities/pagination.py +80 -0
  156. fastmcp/utilities/skills.py +253 -0
  157. fastmcp/utilities/tests.py +0 -16
  158. fastmcp/utilities/timeout.py +47 -0
  159. fastmcp/utilities/types.py +1 -1
  160. fastmcp/utilities/versions.py +285 -0
  161. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
  162. fastmcp-3.0.0b1.dist-info/RECORD +228 -0
  163. fastmcp/client/transports.py +0 -1170
  164. fastmcp/contrib/component_manager/component_service.py +0 -209
  165. fastmcp/prompts/prompt_manager.py +0 -117
  166. fastmcp/resources/resource_manager.py +0 -338
  167. fastmcp/server/tasks/converters.py +0 -206
  168. fastmcp/server/tasks/protocol.py +0 -359
  169. fastmcp/tools/tool_manager.py +0 -170
  170. fastmcp/utilities/mcp_config.py +0 -56
  171. fastmcp-2.14.4.dist-info/RECORD +0 -161
  172. /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
  173. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
  174. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
  175. {fastmcp-2.14.4.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 TYPE_CHECKING, Annotated, Any, Literal
8
+ from typing import Annotated, Any, Literal
9
9
 
10
10
  from platformdirs import user_data_dir
11
- from pydantic import Field, ImportString, field_validator
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
- show_cli_banner: Annotated[
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 via CLI.
388
- This setting can be overridden by the --no-banner CLI flag.
389
- Set to False via FASTMCP_SHOW_CLI_BANNER=false to suppress the banner.
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
- @property
411
- def server_auth_class(self) -> AuthProvider | None:
412
- from fastmcp.utilities.types import get_cached_typeadapter
413
-
414
- if not self.server_auth:
415
- return None
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
- return auth_class
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"