Rename Enabled transform to Visibility (#2950)

This commit is contained in:
Jeremiah Lowin 2026-01-19 20:46:58 -05:00 committed by GitHub
commit 0e4d519c86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 245 additions and 236 deletions

View file

@ -23,7 +23,7 @@ class Provider:
Providers support:
- **Lifecycle management**: `async def lifespan()` for setup/teardown
- **Enabled control**: `enable()` / `disable()` with name, version, tags, components, and allowlist mode
- **Visibility control**: `enable()` / `disable()` with name, version, tags, components, and allowlist mode
- **Transform stacking**: `provider.add_transform(Namespace(...))`, `provider.add_transform(ToolTransform(...))`
### LocalProvider
@ -106,7 +106,7 @@ Transforms modify components (tools, resources, prompts) as they flow from provi
- `Namespace` - adds prefixes to names (`tool` → `api_tool`) and path segments to URIs (`data://x` → `data://api/x`)
- `ToolTransform` - modifies tool schemas (rename, description, tags, argument transforms)
- `Enabled` - sets enabled state on components by key or tag (backs `enable()`/`disable()` API)
- `Visibility` - sets visibility state on components by key or tag (backs `enable()`/`disable()` API)
- `VersionFilter` - filters components by version range (`version_gte`, `version_lt`)
- `ResourcesAsTools` - exposes resources as tools for tool-only clients
- `PromptsAsTools` - exposes prompts as tools for tool-only clients
@ -149,7 +149,7 @@ Transforms apply at two levels:
- **Provider-level**: `provider.add_transform()` - affects only that provider's components
- **Server-level**: `server.add_transform()` - affects all components from all providers
Documentation: `docs/servers/providers/transforms.mdx`, `docs/servers/enabled.mdx`
Documentation: `docs/servers/providers/transforms.mdx`, `docs/servers/visibility.mdx`
### ResourcesAsTools and PromptsAsTools
@ -232,9 +232,9 @@ Documentation: `docs/servers/context.mdx`
---
## Enabled System
## Visibility System
Components can be enabled/disabled using the enabled system. Each `enable()` or `disable()` call adds a stateless Enabled transform that marks components via internal metadata. Later transforms override earlier ones.
Components can be enabled/disabled using the visibility system. Each `enable()` or `disable()` call adds a stateless Visibility transform that marks components via internal metadata. Later transforms override earlier ones.
```python
mcp = FastMCP("Server")
@ -261,7 +261,7 @@ Works at both server and provider level. Supports:
- **Allowlist mode** (`only=True`): Only explicitly enabled components visible
- **Tag-based filtering**: Enable/disable groups of components by tag
- **Override semantics**: Later transforms override earlier marks (enable after disable = enabled)
- **Transform ordering**: Enabled transforms are injected at the point you call them, so component state is known
- **Transform ordering**: Visibility transforms are injected at the point you call them, so component state is known
### Per-Session Visibility
@ -286,7 +286,7 @@ async def unlock_premium(ctx: Context) -> str:
@mcp.tool
async def reset_features(ctx: Context) -> str:
"""Reset to default feature set."""
await ctx.reset_components()
await ctx.reset_visibility()
return "Features reset to defaults"
# Globally disabled - sessions unlock individually
@ -296,11 +296,11 @@ mcp.disable(tags={"premium"})
Session visibility methods:
- `await ctx.enable_components(...)`: Enable components for this session
- `await ctx.disable_components(...)`: Disable components for this session
- `await ctx.reset_components()`: Clear session rules, return to global defaults
- `await ctx.reset_visibility()`: Clear session rules, return to global defaults
Session rules override global transforms. FastMCP automatically sends `ToolListChangedNotification` (and resource/prompt equivalents) to affected sessions when visibility changes.
Documentation: `docs/servers/enabled.mdx`
Documentation: `docs/servers/visibility.mdx`
---

View file

@ -137,7 +137,7 @@
"servers/tasks",
"servers/telemetry",
"servers/versioning",
"servers/enabled"
"servers/visibility"
]
},
{
@ -563,7 +563,7 @@
"group": "transforms",
"pages": [
"python-sdk/fastmcp-server-transforms-__init__",
"python-sdk/fastmcp-server-transforms-enabled",
"python-sdk/fastmcp-server-transforms-visibility",
"python-sdk/fastmcp-server-transforms-namespace",
"python-sdk/fastmcp-server-transforms-prompts_as_tools",
"python-sdk/fastmcp-server-transforms-resources_as_tools",

View file

@ -591,7 +591,7 @@ Enable components matching criteria for this session only.
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -615,7 +615,7 @@ Disable components matching criteria for this session only.
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -629,10 +629,10 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `match_all`: If True, matches all components regardless of other criteria.
#### `reset_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L1073" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `reset_visibility` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L1073" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
reset_components(self) -> None
reset_visibility(self) -> None
```
Clear all session visibility rules.

View file

@ -121,7 +121,7 @@ get_tool(self, name: str, version: VersionSpec | None = None) -> Tool | None
Get tool by transformed name with all transforms applied.
Note: This method does NOT filter disabled components. The Server
(FastMCP) performs enabled filtering after all transforms complete,
(FastMCP) performs visibility filtering after all transforms complete,
allowing session-level transforms to override provider-level disables.
**Args:**
@ -152,7 +152,7 @@ get_resource(self, uri: str, version: VersionSpec | None = None) -> Resource | N
Get resource by transformed URI with all transforms applied.
Note: This method does NOT filter disabled components. The Server
(FastMCP) performs enabled filtering after all transforms complete.
(FastMCP) performs visibility filtering after all transforms complete.
**Args:**
- `uri`: The transformed resource URI to look up.
@ -182,7 +182,7 @@ get_resource_template(self, uri: str, version: VersionSpec | None = None) -> Res
Get resource template by transformed URI with all transforms applied.
Note: This method does NOT filter disabled components. The Server
(FastMCP) performs enabled filtering after all transforms complete.
(FastMCP) performs visibility filtering after all transforms complete.
**Args:**
- `uri`: The transformed template URI to look up.
@ -212,7 +212,7 @@ get_prompt(self, name: str, version: VersionSpec | None = None) -> Prompt | None
Get prompt by transformed name with all transforms applied.
Note: This method does NOT filter disabled components. The Server
(FastMCP) performs enabled filtering after all transforms complete.
(FastMCP) performs visibility filtering after all transforms complete.
**Args:**
- `name`: The transformed prompt name to look up.
@ -289,7 +289,7 @@ disable(self) -> Self
Disable components matching all specified criteria.
Adds an enabled transform that marks matching components as disabled.
Adds a visibility transform that marks matching components as disabled.
Components can be re-enabled by calling enable() with matching criteria
(the later transform wins).

View file

@ -191,7 +191,7 @@ list_tools(self) -> Sequence[Tool]
List all enabled tools from providers.
Overrides Provider.list_tools() to add enabled filtering, auth filtering,
Overrides Provider.list_tools() to add visibility filtering, auth filtering,
and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
@ -204,7 +204,7 @@ get_tool(self, name: str, version: VersionSpec | None = None) -> Tool | None
Get a tool by name, filtering disabled tools.
Overrides Provider.get_tool() to add enabled filtering after all
Overrides Provider.get_tool() to add visibility filtering after all
transforms (including session-level) have been applied. This ensures
session transforms can override provider-level disables.
@ -224,7 +224,7 @@ list_resources(self) -> Sequence[Resource]
List all enabled resources from providers.
Overrides Provider.list_resources() to add enabled filtering, auth filtering,
Overrides Provider.list_resources() to add visibility filtering, auth filtering,
and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
@ -237,7 +237,7 @@ get_resource(self, uri: str, version: VersionSpec | None = None) -> Resource | N
Get a resource by URI, filtering disabled resources.
Overrides Provider.get_resource() to add enabled filtering after all
Overrides Provider.get_resource() to add visibility filtering after all
transforms (including session-level) have been applied.
**Args:**
@ -256,7 +256,7 @@ list_resource_templates(self) -> Sequence[ResourceTemplate]
List all enabled resource templates from providers.
Overrides Provider.list_resource_templates() to add enabled filtering,
Overrides Provider.list_resource_templates() to add visibility filtering,
auth filtering, and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
@ -269,7 +269,7 @@ get_resource_template(self, uri: str, version: VersionSpec | None = None) -> Res
Get a resource template by URI, filtering disabled templates.
Overrides Provider.get_resource_template() to add enabled filtering after
Overrides Provider.get_resource_template() to add visibility filtering after
all transforms (including session-level) have been applied.
**Args:**
@ -288,7 +288,7 @@ list_prompts(self) -> Sequence[Prompt]
List all enabled prompts from providers.
Overrides Provider.list_prompts() to add enabled filtering, auth filtering,
Overrides Provider.list_prompts() to add visibility filtering, auth filtering,
and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
@ -301,7 +301,7 @@ get_prompt(self, name: str, version: VersionSpec | None = None) -> Prompt | None
Get a prompt by name, filtering disabled prompts.
Overrides Provider.get_prompt() to add enabled filtering after all
Overrides Provider.get_prompt() to add visibility filtering after all
transforms (including session-level) have been applied.
**Args:**

View file

@ -1,21 +1,21 @@
---
title: enabled
sidebarTitle: enabled
title: visibility
sidebarTitle: visibility
---
# `fastmcp.server.transforms.enabled`
# `fastmcp.server.transforms.visibility`
Enabled transform for marking component enabled state.
Visibility transform for marking component visibility state.
Each Enabled instance marks components via internal metadata. Multiple
enabled transforms can be stacked - later transforms override earlier ones.
Each Visibility instance marks components via internal metadata. Multiple
visibility transforms can be stacked - later transforms override earlier ones.
Final filtering happens at the Provider level.
## Functions
### `is_enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `is_enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
is_enabled(component: FastMCPComponent) -> bool
@ -25,10 +25,10 @@ is_enabled(component: FastMCPComponent) -> bool
Check if component is enabled.
Returns True if:
- No enabled mark exists (default is enabled)
- Enabled mark is True
- No visibility mark exists (default is enabled)
- Visibility mark is True
Returns False if enabled mark is False.
Returns False if visibility mark is False.
**Args:**
- `component`: Component to check.
@ -37,7 +37,7 @@ Returns False if enabled mark is False.
- True if component should be enabled/visible to clients.
### `get_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `get_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_visibility_rules(context: Context) -> list[dict[str, Any]]
@ -47,7 +47,7 @@ get_visibility_rules(context: Context) -> list[dict[str, Any]]
Load visibility rule dicts from session state.
### `save_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L303" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `save_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L303" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
save_visibility_rules(context: Context, rules: list[dict[str, Any]]) -> None
@ -64,27 +64,27 @@ If None, sends notifications for all types (safe default).
If provided, only sends notifications for specified types.
### `create_enabled_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `create_visibility_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]
create_visibility_transforms(rules: list[dict[str, Any]]) -> list[Visibility]
```
Convert rule dicts to Enabled transforms.
Convert rule dicts to Visibility transforms.
### `get_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L358" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `get_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L358" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_session_transforms(context: Context) -> list[Enabled]
get_session_transforms(context: Context) -> list[Visibility]
```
Get session-specific Enabled transforms from state store.
Get session-specific Visibility transforms from state store.
### `enable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `enable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
enable_components(context: Context) -> None
@ -95,7 +95,7 @@ Enable components matching criteria for this session only.
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -110,7 +110,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `match_all`: If True, matches all components regardless of other criteria.
### `disable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L424" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `disable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L424" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
disable_components(context: Context) -> None
@ -121,7 +121,7 @@ Disable components matching criteria for this session only.
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -136,10 +136,10 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `match_all`: If True, matches all components regardless of other criteria.
### `reset_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L478" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `reset_visibility` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L478" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
reset_components(context: Context) -> None
reset_visibility(context: Context) -> None
```
@ -154,7 +154,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `context`: The context for this session.
### `apply_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L495" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `apply_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L495" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
apply_session_transforms(components: Sequence[ComponentT]) -> Sequence[ComponentT]
@ -164,7 +164,7 @@ apply_session_transforms(components: Sequence[ComponentT]) -> Sequence[Component
Apply session-specific visibility transforms to components.
This helper applies session-level enable/disable rules by marking
components with their enabled state. Session transforms override
components with their visibility state. Session transforms override
global transforms due to mark-based semantics (later marks win).
**Args:**
@ -176,28 +176,28 @@ global transforms due to mark-based semantics (later marks win).
## Classes
### `Enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `Visibility` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Sets enabled state on matching components.
Sets visibility state on matching components.
Does NOT filter inline - just marks components with enabled state.
Does NOT filter inline - just marks components with visibility state.
Later transforms in the chain can override earlier marks.
Final filtering happens at the Provider level after all transforms run.
**Methods:**
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L194" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L194" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]
```
Mark tools by enabled state.
Mark tools by visibility state.
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_tool(self, name: str, call_next: GetToolNext) -> Tool | None
@ -206,16 +206,16 @@ get_tool(self, name: str, call_next: GetToolNext) -> Tool | None
Mark tool if found.
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]
```
Mark resources by enabled state.
Mark resources by visibility state.
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_resource(self, uri: str, call_next: GetResourceNext) -> Resource | None
@ -224,16 +224,16 @@ get_resource(self, uri: str, call_next: GetResourceNext) -> Resource | None
Mark resource if found.
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L232" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L232" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_resource_templates(self, templates: Sequence[ResourceTemplate]) -> Sequence[ResourceTemplate]
```
Mark resource templates by enabled state.
Mark resource templates by visibility state.
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L238" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L238" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_resource_template(self, uri: str, call_next: GetResourceTemplateNext) -> ResourceTemplate | None
@ -242,20 +242,19 @@ get_resource_template(self, uri: str, call_next: GetResourceTemplateNext) -> Res
Mark resource template if found.
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]
```
Mark prompts by enabled state.
Mark prompts by visibility state.
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_prompt(self, name: str, call_next: GetPromptNext) -> Prompt | None
```
Mark prompt if found.

View file

@ -23,7 +23,7 @@ The `Context` object provides a clean interface to access MCP features within yo
- **LLM Sampling**: Request the client's LLM to generate text based on provided messages
- **User Elicitation**: Request structured input from users during tool execution
- **Session State**: Store data that persists across requests within an MCP session
- **Session Visibility**: [Control which components are visible](/servers/enabled#per-session-visibility) to the current session
- **Session Visibility**: [Control which components are visible](/servers/visibility#per-session-visibility) to the current session
- **Request Information**: Access metadata about the current request
- **Server Access**: When needed, access the underlying FastMCP server instance
@ -266,7 +266,7 @@ State set during `on_initialize` middleware persists to subsequent tool calls wh
<VersionBadge version="3.0.0" />
Tools can customize which components are visible to their current session using `ctx.enable_components()`, `ctx.disable_components()`, and `ctx.reset_components()`. These methods apply visibility rules that affect only the calling session, leaving other sessions unchanged. See [Per-Session Visibility](/servers/enabled#per-session-visibility) for complete documentation, filter criteria, and patterns like namespace activation.
Tools can customize which components are visible to their current session using `ctx.enable_components()`, `ctx.disable_components()`, and `ctx.reset_visibility()`. These methods apply visibility rules that affect only the calling session, leaving other sessions unchanged. See [Per-Session Visibility](/servers/visibility#per-session-visibility) for complete documentation, filter criteria, and patterns like namespace activation.
### Change Notifications

View file

@ -354,7 +354,7 @@ mcp.disable(tags={"internal"})
mcp.enable(tags={"public"}, only=True)
```
See [Enabled](/servers/enabled) for the complete enabled control API including key formats, tag-based filtering, and provider-level control.
See [Visibility](/servers/visibility) for the complete visibility control API including key formats, tag-based filtering, and provider-level control.
### Async Prompts

View file

@ -29,7 +29,7 @@ Both providers and [middleware](/servers/middleware) can influence what componen
**Middleware** intercepts individual requests. It's well-suited for request-specific decisions like logging, rate limiting, or authentication.
You *could* use middleware to dynamically add tools based on request context. But it's often cleaner to have a provider source all possible tools, then use middleware or [enabled controls](/servers/enabled) to filter what each request can see. This separation makes it easier to reason about how components are sourced and how they interact with other server machinery.
You *could* use middleware to dynamically add tools based on request context. But it's often cleaner to have a provider source all possible tools, then use middleware or [visibility controls](/servers/visibility) to filter what each request can see. This separation makes it easier to reason about how components are sourced and how they interact with other server machinery.
## The Provider Interface

View file

@ -126,7 +126,7 @@ mcp.disable(tags={"admin"})
mcp.enable(keys={"tool:get_status"}, only=True)
```
See [Enabled](/servers/enabled) for the full documentation on keys, tags, allowlist mode, and provider-level control.
See [Visibility](/servers/visibility) for the full documentation on keys, tags, allowlist mode, and provider-level control.
## Standalone LocalProvider
@ -157,4 +157,4 @@ This is useful for:
- Testing components in isolation
- Building reusable component libraries
Standalone providers also support enabled control with `enable()` and `disable()`. See [Enabled](/servers/enabled) for details.
Standalone providers also support visibility control with `enable()` and `disable()`. See [Visibility](/servers/visibility) for details.

View file

@ -67,7 +67,7 @@ When a client requests a tool, FastMCP queries providers in registration order.
**Learn about providers when** you want to:
- [Mount another server](/servers/providers/mounting) into yours
- [Proxy a remote server](/servers/providers/proxy) through yours
- [Control enabled state](/servers/enabled) of components
- [Control visibility state](/servers/visibility) of components
- [Build dynamic sources](/servers/providers/custom) like database-backed tools
## Next Steps
@ -76,5 +76,5 @@ When a client requests a tool, FastMCP queries providers in registration order.
- [Mounting](/servers/providers/mounting) - Compose servers together
- [Proxying](/servers/providers/proxy) - Connect to remote servers
- [Transforms](/servers/providers/transforms) - Namespace, rename, and modify components
- [Enabled](/servers/enabled) - Control which components clients can access
- [Visibility](/servers/visibility) - Control which components clients can access
- [Custom](/servers/providers/custom) - Build your own providers

View file

@ -240,7 +240,7 @@ mcp.disable(tags={"internal"})
mcp.enable(tags={"public"}, only=True)
```
See [Enabled](/servers/enabled) for the complete enabled control API including key formats, tag-based filtering, and provider-level control.
See [Visibility](/servers/visibility) for the complete visibility control API including key formats, tag-based filtering, and provider-level control.
### Accessing MCP Context

View file

@ -862,7 +862,7 @@ mcp.disable(tags={"admin"})
mcp.enable(tags={"public"}, only=True)
```
See [Enabled](/servers/enabled) for the complete enabled control API including key formats, tag-based filtering, and provider-level control.
See [Visibility](/servers/visibility) for the complete visibility control API including key formats, tag-based filtering, and provider-level control.
## MCP Annotations

View file

@ -59,7 +59,7 @@ mcp.enable(tags={"admin"})
## Keys and Tags
Enabled filtering works with two identifiers: keys (for specific components) and tags (for groups).
Visibility filtering works with two identifiers: keys (for specific components) and tags (for groups).
### Component Keys
@ -123,7 +123,7 @@ mcp.disable(keys={"tool:debug_info"}, tags={"dangerous"})
## Allowlist Mode
By default, enabled filtering uses blocklist mode: everything is enabled unless explicitly disabled. The `only=True` parameter switches to allowlist mode, where **only** specified components are enabled.
By default, visibility filtering uses blocklist mode: everything is enabled unless explicitly disabled. The `only=True` parameter switches to allowlist mode, where **only** specified components are enabled.
```python
from fastmcp import FastMCP
@ -159,7 +159,7 @@ Allowlist mode is useful for restrictive environments where you want to explicit
When you call `enable(only=True)`:
1. Default enabled state switches to "disabled"
1. Default visibility state switches to "disabled"
2. Previous allowlists are cleared
3. Only specified keys/tags become enabled
@ -186,11 +186,11 @@ You can always re-enable something that was disabled by adding another `enable()
## Server vs Provider
Enabled state operates at two levels: the server and individual providers.
Visibility state operates at two levels: the server and individual providers.
### Server-Level
Server-level enabled state applies to all components from all providers. When you call `mcp.enable()` or `mcp.disable()`, you're filtering the final view that clients see.
Server-level visibility state applies to all components from all providers. When you call `mcp.enable()` or `mcp.disable()`, you're filtering the final view that clients see.
```python
from fastmcp import FastMCP
@ -208,13 +208,13 @@ main.disable(tags={"internal"})
### Provider-Level
Each provider can add its own enabled transforms. These run before server-level transforms, so the server can override provider-level disables.
Each provider can add its own visibility transforms. These run before server-level transforms, so the server can override provider-level disables.
```python
from fastmcp import FastMCP
from fastmcp.server.providers import LocalProvider
# Create provider with enabled control
# Create provider with visibility control
admin_tools = LocalProvider()
@admin_tools.tool(tags={"admin"})
@ -261,7 +261,7 @@ mcp.disable(tags={"beta"})
## Dynamic Changes
Enabled state changes take effect immediately. You can adjust during request handling based on context.
Visibility state changes take effect immediately. You can adjust during request handling based on context.
```python
from fastmcp import FastMCP
@ -312,7 +312,7 @@ async def unlock_premium(ctx: Context) -> str:
@mcp.tool
async def reset_features(ctx: Context) -> str:
"""Reset to default feature set."""
await ctx.reset_components()
await ctx.reset_visibility()
return "Features reset to defaults"
# Premium tools are disabled globally by default
@ -419,7 +419,7 @@ async def activate_admin(ctx: Context) -> str:
@server.tool
async def deactivate_all(ctx: Context) -> str:
await ctx.reset_components()
await ctx.reset_visibility()
return "All namespaces deactivated"
# Disable namespace tools globally
@ -432,11 +432,11 @@ Sessions start seeing only the activation tools. Calling `activate_finance` reve
- **`await ctx.enable_components(...) -> None`**: Enable matching components for this session
- **`await ctx.disable_components(...) -> None`**: Disable matching components for this session
- **`await ctx.reset_components() -> None`**: Clear all session rules, returning to global defaults
- **`await ctx.reset_visibility() -> None`**: Clear all session rules, returning to global defaults
## Client Notifications
When enabled state changes, FastMCP automatically notifies connected clients. Clients supporting the MCP notification protocol receive `list_changed` events and can refresh their component lists.
When visibility state changes, FastMCP automatically notifies connected clients. Clients supporting the MCP notification protocol receive `list_changed` events and can refresh their component lists.
This happens automatically. You don't need to trigger notifications manually.
@ -449,23 +449,23 @@ mcp.disable(tags={"maintenance"})
## Filtering Logic
Understanding the filtering logic helps when debugging enabled state issues.
Understanding the filtering logic helps when debugging visibility state issues.
The `is_enabled()` function checks a component's internal metadata:
1. If the component has `meta.fastmcp._internal.enabled = False`, it's disabled
2. If the component has `meta.fastmcp._internal.enabled = True`, it's enabled
3. If no enabled state is set, the component is enabled by default
1. If the component has `meta.fastmcp._internal.visibility = False`, it's disabled
2. If the component has `meta.fastmcp._internal.visibility = True`, it's enabled
3. If no visibility state is set, the component is enabled by default
When multiple `enable()` and `disable()` calls are made, transforms are applied in order. **Later transforms override earlier ones**, so the last matching transform wins.
## The Enabled Transform
## The Visibility Transform
Under the hood, `enable()` and `disable()` add `Enabled` transforms to the server or provider. The `Enabled` transform marks components with enabled metadata, and the server applies the final filter after all provider and server transforms complete.
Under the hood, `enable()` and `disable()` add `Visibility` transforms to the server or provider. The `Visibility` transform marks components with visibility metadata, and the server applies the final filter after all provider and server transforms complete.
```python
from fastmcp import FastMCP
from fastmcp.server.transforms import Enabled
from fastmcp.server.transforms import Visibility
mcp = FastMCP("Server")
@ -473,7 +473,7 @@ mcp = FastMCP("Server")
mcp.disable(names={"secret_tool"})
# Equivalent to:
mcp.add_transform(Enabled(False, names={"secret_tool"}))
mcp.add_transform(Visibility(False, names={"secret_tool"}))
```
Server-level transforms override provider-level transforms. If a component is disabled at the provider level but enabled at the server level, the server-level `enable()` can re-enable it.

View file

@ -61,7 +61,7 @@ async def activate_admin(ctx: Context) -> str:
@server.tool
async def deactivate_all(ctx: Context) -> str:
"""Deactivate all namespaces, returning to defaults."""
await ctx.reset_components()
await ctx.reset_visibility()
return "All namespaces deactivated"

View file

@ -39,23 +39,23 @@ from fastmcp.server.sampling.run import (
sample_step_impl,
)
from fastmcp.server.server import FastMCP, StateValue
from fastmcp.server.transforms.enabled import (
Enabled,
from fastmcp.server.transforms.visibility import (
Visibility,
)
from fastmcp.server.transforms.enabled import (
from fastmcp.server.transforms.visibility import (
disable_components as _disable_components,
)
from fastmcp.server.transforms.enabled import (
from fastmcp.server.transforms.visibility import (
enable_components as _enable_components,
)
from fastmcp.server.transforms.enabled import (
from fastmcp.server.transforms.visibility import (
get_session_transforms as _get_session_transforms,
)
from fastmcp.server.transforms.enabled import (
from fastmcp.server.transforms.visibility import (
get_visibility_rules as _get_visibility_rules,
)
from fastmcp.server.transforms.enabled import (
reset_components as _reset_components,
from fastmcp.server.transforms.visibility import (
reset_visibility as _reset_visibility,
)
from fastmcp.utilities.logging import _clamp_logger, get_logger
from fastmcp.utilities.versions import VersionSpec
@ -990,8 +990,8 @@ class Context:
"""Load visibility rule dicts from session state."""
return await _get_visibility_rules(self)
async def _get_session_transforms(self) -> list[Enabled]:
"""Get session-specific Enabled transforms from state store."""
async def _get_session_transforms(self) -> list[Visibility]:
"""Get session-specific Visibility transforms from state store."""
return await _get_session_transforms(self)
async def enable_components(
@ -1009,7 +1009,7 @@ class Context:
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -1047,7 +1047,7 @@ class Context:
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -1070,7 +1070,7 @@ class Context:
match_all=match_all,
)
async def reset_components(self) -> None:
async def reset_visibility(self) -> None:
"""Clear all session visibility rules.
Use this to reset session visibility back to global defaults.
@ -1078,7 +1078,7 @@ class Context:
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
"""
await _reset_components(self)
await _reset_visibility(self)
async def _log_to_server_and_client(

View file

@ -38,7 +38,7 @@ from typing_extensions import Self
from fastmcp.prompts.prompt import Prompt
from fastmcp.resources.resource import Resource
from fastmcp.resources.template import ResourceTemplate
from fastmcp.server.transforms.enabled import Enabled
from fastmcp.server.transforms.visibility import Visibility
from fastmcp.tools.tool import Tool
from fastmcp.utilities.async_utils import gather
from fastmcp.utilities.components import FastMCPComponent
@ -502,7 +502,7 @@ class Provider:
) -> Self:
"""Enable components matching all specified criteria.
Adds an enabled transform that marks matching components as enabled.
Adds a visibility transform that marks matching components as enabled.
Later transforms override earlier ones, so enable after disable makes
the component enabled.
@ -524,9 +524,9 @@ class Provider:
if only:
# Allowlist: disable everything, then enable matching
# The enable transform runs later on return path, so it overrides
self._transforms.append(Enabled(False, match_all=True))
self._transforms.append(Visibility(False, match_all=True))
self._transforms.append(
Enabled(
Visibility(
True,
names=names,
keys=keys,
@ -550,7 +550,7 @@ class Provider:
) -> Self:
"""Disable components matching all specified criteria.
Adds an enabled transform that marks matching components as disabled.
Adds a visibility transform that marks matching components as disabled.
Components can be re-enabled by calling enable() with matching criteria
(the later transform wins).
@ -566,7 +566,7 @@ class Provider:
Self for method chaining.
"""
self._transforms.append(
Enabled(
Visibility(
False,
names=names,
keys=keys,

View file

@ -72,7 +72,7 @@ from fastmcp.server.transforms import (
ToolTransform,
Transform,
)
from fastmcp.server.transforms.enabled import apply_session_transforms, is_enabled
from fastmcp.server.transforms.visibility import apply_session_transforms, is_enabled
from fastmcp.settings import DuplicateBehavior as DuplicateBehaviorSetting
from fastmcp.settings import Settings
from fastmcp.tools.function_tool import FunctionTool
@ -604,7 +604,7 @@ class FastMCP(
async def list_tools(self, *, run_middleware: bool = True) -> Sequence[Tool]:
"""List all enabled tools from providers.
Overrides Provider.list_tools() to add enabled filtering, auth filtering,
Overrides Provider.list_tools() to add visibility filtering, auth filtering,
and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
"""
@ -676,7 +676,7 @@ class FastMCP(
) -> Tool | None:
"""Get a tool by name, filtering disabled tools.
Overrides Provider.get_tool() to add enabled filtering after all
Overrides Provider.get_tool() to add visibility filtering after all
transforms (including session-level) have been applied. This ensures
session transforms can override provider-level disables.
@ -702,7 +702,7 @@ class FastMCP(
) -> Sequence[Resource]:
"""List all enabled resources from providers.
Overrides Provider.list_resources() to add enabled filtering, auth filtering,
Overrides Provider.list_resources() to add visibility filtering, auth filtering,
and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
"""
@ -774,7 +774,7 @@ class FastMCP(
) -> Resource | None:
"""Get a resource by URI, filtering disabled resources.
Overrides Provider.get_resource() to add enabled filtering after all
Overrides Provider.get_resource() to add visibility filtering after all
transforms (including session-level) have been applied.
Args:
@ -799,7 +799,7 @@ class FastMCP(
) -> Sequence[ResourceTemplate]:
"""List all enabled resource templates from providers.
Overrides Provider.list_resource_templates() to add enabled filtering,
Overrides Provider.list_resource_templates() to add visibility filtering,
auth filtering, and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
"""
@ -873,7 +873,7 @@ class FastMCP(
) -> ResourceTemplate | None:
"""Get a resource template by URI, filtering disabled templates.
Overrides Provider.get_resource_template() to add enabled filtering after
Overrides Provider.get_resource_template() to add visibility filtering after
all transforms (including session-level) have been applied.
Args:
@ -896,7 +896,7 @@ class FastMCP(
async def list_prompts(self, *, run_middleware: bool = True) -> Sequence[Prompt]:
"""List all enabled prompts from providers.
Overrides Provider.list_prompts() to add enabled filtering, auth filtering,
Overrides Provider.list_prompts() to add visibility filtering, auth filtering,
and middleware execution. Returns all versions (no deduplication).
Protocol handlers deduplicate for MCP wire format.
"""
@ -968,7 +968,7 @@ class FastMCP(
) -> Prompt | None:
"""Get a prompt by name, filtering disabled prompts.
Overrides Provider.get_prompt() to add enabled filtering after all
Overrides Provider.get_prompt() to add visibility filtering after all
transforms (including session-level) have been applied.
Args:

View file

@ -220,7 +220,7 @@ class Transform:
# Re-export built-in transforms (must be after Transform class to avoid circular imports)
from fastmcp.server.transforms.enabled import Enabled, is_enabled # noqa: E402
from fastmcp.server.transforms.visibility import Visibility, is_enabled # noqa: E402
from fastmcp.server.transforms.namespace import Namespace # noqa: E402
from fastmcp.server.transforms.prompts_as_tools import PromptsAsTools # noqa: E402
from fastmcp.server.transforms.resources_as_tools import ResourcesAsTools # noqa: E402
@ -228,7 +228,6 @@ from fastmcp.server.transforms.tool_transform import ToolTransform # noqa: E402
from fastmcp.server.transforms.version_filter import VersionFilter # noqa: E402
__all__ = [
"Enabled",
"GetPromptNext",
"GetResourceNext",
"GetResourceTemplateNext",
@ -240,5 +239,6 @@ __all__ = [
"Transform",
"VersionFilter",
"VersionSpec",
"Visibility",
"is_enabled",
]

View file

@ -1,7 +1,7 @@
"""Enabled transform for marking component enabled state.
"""Visibility transform for marking component visibility state.
Each Enabled instance marks components via internal metadata. Multiple
enabled transforms can be stacked - later transforms override earlier ones.
Each Visibility instance marks components via internal metadata. Multiple
visibility transforms can be stacked - later transforms override earlier ones.
Final filtering happens at the Provider level.
"""
@ -31,29 +31,29 @@ if TYPE_CHECKING:
T = TypeVar("T", bound="FastMCPComponent")
# Enabled state stored at meta["fastmcp"]["_internal"]["enabled"]
# Visibility state stored at meta["fastmcp"]["_internal"]["visibility"]
_FASTMCP_KEY = "fastmcp"
_INTERNAL_KEY = "_internal"
class Enabled(Transform):
"""Sets enabled state on matching components.
class Visibility(Transform):
"""Sets visibility state on matching components.
Does NOT filter inline - just marks components with enabled state.
Does NOT filter inline - just marks components with visibility state.
Later transforms in the chain can override earlier marks.
Final filtering happens at the Provider level after all transforms run.
Example:
```python
# Disable components tagged "internal"
Enabled(False, tags={"internal"})
Visibility(False, tags={"internal"})
# Re-enable specific tool (override earlier disable)
Enabled(True, names={"safe_tool"})
Visibility(True, names={"safe_tool"})
# Allowlist via composition:
Enabled(False, match_all=True) # disable everything
Enabled(True, tags={"public"}) # enable public
Visibility(False, match_all=True) # disable everything
Visibility(True, tags={"public"}) # enable public
```
"""
@ -69,7 +69,7 @@ class Enabled(Transform):
| None = None,
match_all: bool = False,
) -> None:
"""Initialize an enabled marker.
"""Initialize a visibility marker.
Args:
enabled: If True, mark matching as enabled; if False, mark as disabled.
@ -92,7 +92,7 @@ class Enabled(Transform):
def __repr__(self) -> str:
action = "enable" if self._enabled else "disable"
if self.match_all:
return f"Enabled({self._enabled}, match_all=True)"
return f"Visibility({self._enabled}, match_all=True)"
parts = []
if self.names:
parts.append(f"names={set(self.names)}")
@ -105,8 +105,8 @@ class Enabled(Transform):
if self.tags:
parts.append(f"tags={set(self.tags)}")
if parts:
return f"Enabled({action}, {', '.join(parts)})"
return f"Enabled({action})"
return f"Visibility({action}, {', '.join(parts)})"
return f"Visibility({action})"
def _matches(self, component: FastMCPComponent) -> bool:
"""Check if this transform applies to the component.
@ -171,18 +171,20 @@ class Enabled(Transform):
return self.tags is None or bool(component.tags & self.tags)
def _mark_component(self, component: T) -> T:
"""Set enabled state in component metadata if rule matches."""
"""Set visibility state in component metadata if rule matches."""
if not self._matches(component):
return component
# Create new dicts to avoid mutating shared dicts
# (e.g., when Tool.from_tool shares the meta dict between tools)
if component.meta is None:
component.meta = {_FASTMCP_KEY: {_INTERNAL_KEY: {"enabled": self._enabled}}}
component.meta = {
_FASTMCP_KEY: {_INTERNAL_KEY: {"visibility": self._enabled}}
}
else:
old_fastmcp = component.meta.get(_FASTMCP_KEY, {})
old_internal = old_fastmcp.get(_INTERNAL_KEY, {})
new_internal = {**old_internal, "enabled": self._enabled}
new_internal = {**old_internal, "visibility": self._enabled}
new_fastmcp = {**old_fastmcp, _INTERNAL_KEY: new_internal}
component.meta = {**component.meta, _FASTMCP_KEY: new_fastmcp}
return component
@ -192,7 +194,7 @@ class Enabled(Transform):
# -------------------------------------------------------------------------
async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
"""Mark tools by enabled state."""
"""Mark tools by visibility state."""
return [self._mark_component(t) for t in tools]
async def get_tool(
@ -209,7 +211,7 @@ class Enabled(Transform):
# -------------------------------------------------------------------------
async def list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]:
"""Mark resources by enabled state."""
"""Mark resources by visibility state."""
return [self._mark_component(r) for r in resources]
async def get_resource(
@ -232,7 +234,7 @@ class Enabled(Transform):
async def list_resource_templates(
self, templates: Sequence[ResourceTemplate]
) -> Sequence[ResourceTemplate]:
"""Mark resource templates by enabled state."""
"""Mark resource templates by visibility state."""
return [self._mark_component(t) for t in templates]
async def get_resource_template(
@ -253,7 +255,7 @@ class Enabled(Transform):
# -------------------------------------------------------------------------
async def list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]:
"""Mark prompts by enabled state."""
"""Mark prompts by visibility state."""
return [self._mark_component(p) for p in prompts]
async def get_prompt(
@ -270,10 +272,10 @@ def is_enabled(component: FastMCPComponent) -> bool:
"""Check if component is enabled.
Returns True if:
- No enabled mark exists (default is enabled)
- Enabled mark is True
- No visibility mark exists (default is enabled)
- Visibility mark is True
Returns False if enabled mark is False.
Returns False if visibility mark is False.
Args:
component: Component to check.
@ -284,7 +286,7 @@ def is_enabled(component: FastMCPComponent) -> bool:
meta = component.meta or {}
fastmcp = meta.get(_FASTMCP_KEY, {})
internal = fastmcp.get(_INTERNAL_KEY, {})
return internal.get("enabled", True) # Default True if not set
return internal.get("visibility", True) # Default True if not set
# -------------------------------------------------------------------------
@ -327,8 +329,8 @@ async def save_visibility_rules(
await context.send_notification(mcp.types.PromptListChangedNotification())
def create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]:
"""Convert rule dicts to Enabled transforms."""
def create_visibility_transforms(rules: list[dict[str, Any]]) -> list[Visibility]:
"""Convert rule dicts to Visibility transforms."""
transforms = []
for params in rules:
version = None
@ -340,7 +342,7 @@ def create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]:
eq=version_dict.get("eq"),
)
transforms.append(
Enabled(
Visibility(
params["enabled"],
names=set(params["names"]) if params.get("names") else None,
keys=set(params["keys"]) if params.get("keys") else None,
@ -355,8 +357,8 @@ def create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]:
return transforms
async def get_session_transforms(context: Context) -> list[Enabled]:
"""Get session-specific Enabled transforms from state store."""
async def get_session_transforms(context: Context) -> list[Visibility]:
"""Get session-specific Visibility transforms from state store."""
try:
# Will raise RuntimeError if no session available
_ = context.session_id
@ -364,7 +366,7 @@ async def get_session_transforms(context: Context) -> list[Enabled]:
return []
rules = await get_visibility_rules(context)
return create_enabled_transforms(rules)
return create_visibility_transforms(rules)
async def enable_components(
@ -381,7 +383,7 @@ async def enable_components(
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -435,7 +437,7 @@ async def disable_components(
Session rules override global transforms. Rules accumulate - each call
adds a new rule to the session. Later marks override earlier ones
(Enabled transform semantics).
(Visibility transform semantics).
Sends notifications to this session only: ToolListChangedNotification,
ResourceListChangedNotification, and PromptListChangedNotification.
@ -475,7 +477,7 @@ async def disable_components(
await save_visibility_rules(context, rules, components=components)
async def reset_components(context: Context) -> None:
async def reset_visibility(context: Context) -> None:
"""Clear all session visibility rules.
Use this to reset session visibility back to global defaults.
@ -498,7 +500,7 @@ async def apply_session_transforms(
"""Apply session-specific visibility transforms to components.
This helper applies session-level enable/disable rules by marking
components with their enabled state. Session transforms override
components with their visibility state. Session transforms override
global transforms due to mark-based semantics (later marks win).
Args:

View file

@ -3,7 +3,7 @@
import pytest
from fastmcp import FastMCP
from fastmcp.server.transforms.enabled import Enabled
from fastmcp.server.transforms.visibility import Visibility
class TestIncludeExcludeTagsDeprecation:
@ -20,28 +20,28 @@ class TestIncludeExcludeTagsDeprecation:
FastMCP(include_tags={"public"})
def test_exclude_tags_still_works(self):
"""exclude_tags adds an Enabled transform that disables matching tags."""
"""exclude_tags adds a Visibility transform that disables matching tags."""
with pytest.warns(DeprecationWarning):
mcp = FastMCP(exclude_tags={"internal"})
# Should have added an Enabled transform that disables the tag
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Enabled)]
# Should have added a Visibility transform that disables the tag
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
assert len(enabled_transforms) == 1
e = enabled_transforms[0]
assert e._enabled is False
assert e.tags == {"internal"}
def test_include_tags_still_works(self):
"""include_tags adds Enabled transforms for allowlist mode."""
"""include_tags adds Visibility transforms for allowlist mode."""
with pytest.warns(DeprecationWarning):
mcp = FastMCP(include_tags={"public"})
# Should have added Enabled transforms for allowlist mode
# Should have added Visibility transforms for allowlist mode
# (one to disable all, one to enable matching)
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Enabled)]
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
assert len(enabled_transforms) == 2
# First should disable all (Enabled.all(False))
# First should disable all (Visibility.all(False))
disable_all_transform = enabled_transforms[0]
assert disable_all_transform._enabled is False
assert disable_all_transform.match_all is True
@ -59,7 +59,7 @@ class TestIncludeExcludeTagsDeprecation:
# Should have added transforms for both
# include_tags creates 2 (disable all + enable matching)
# exclude_tags creates 1 (disable matching)
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Enabled)]
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
assert len(enabled_transforms) == 3
# Check we have both tag rules

View file

@ -336,7 +336,7 @@ class TestLocalProviderDecorators:
assert "tool:direct_tool@" in provider._components
def test_tool_enabled_false(self):
"""Tool with enabled=False should add an Enabled transform."""
"""Tool with enabled=False should add a Visibility transform."""
provider = LocalProvider()
@provider.tool(enabled=False)
@ -344,10 +344,12 @@ class TestLocalProviderDecorators:
return "should be disabled"
assert "tool:disabled_tool@" in provider._components
# enabled=False adds an Enabled transform to disable the tool
from fastmcp.server.transforms.enabled import Enabled
# enabled=False adds a Visibility transform to disable the tool
from fastmcp.server.transforms.visibility import Visibility
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
enabled_transforms = [
t for t in provider.transforms if isinstance(t, Visibility)
]
assert len(enabled_transforms) == 1
assert enabled_transforms[0]._enabled is False
assert enabled_transforms[0].keys == {"tool:disabled_tool@"}
@ -425,7 +427,7 @@ class TestLocalProviderDecorators:
assert provider._components["resource:resource://test@"].name == "custom_name"
def test_resource_enabled_false(self):
"""Resource with enabled=False should add an Enabled transform."""
"""Resource with enabled=False should add a Visibility transform."""
provider = LocalProvider()
@provider.resource("resource://test", enabled=False)
@ -433,10 +435,12 @@ class TestLocalProviderDecorators:
return "should be disabled"
assert "resource:resource://test@" in provider._components
# enabled=False adds an Enabled transform to disable the resource
from fastmcp.server.transforms.enabled import Enabled
# enabled=False adds a Visibility transform to disable the resource
from fastmcp.server.transforms.visibility import Visibility
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
enabled_transforms = [
t for t in provider.transforms if isinstance(t, Visibility)
]
assert len(enabled_transforms) == 1
assert enabled_transforms[0]._enabled is False
assert enabled_transforms[0].keys == {"resource:resource://test@"}
@ -461,7 +465,7 @@ class TestLocalProviderDecorators:
assert "resource://disabled" not in uris
def test_template_enabled_false(self):
"""Template with enabled=False should add an Enabled transform."""
"""Template with enabled=False should add a Visibility transform."""
provider = LocalProvider()
@provider.resource("data://{id}", enabled=False)
@ -469,10 +473,12 @@ class TestLocalProviderDecorators:
return f"Data {id}"
assert "template:data://{id}@" in provider._components
# enabled=False adds an Enabled transform to disable the template
from fastmcp.server.transforms.enabled import Enabled
# enabled=False adds a Visibility transform to disable the template
from fastmcp.server.transforms.visibility import Visibility
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
enabled_transforms = [
t for t in provider.transforms if isinstance(t, Visibility)
]
assert len(enabled_transforms) == 1
assert enabled_transforms[0]._enabled is False
assert enabled_transforms[0].keys == {"template:data://{id}@"}
@ -532,7 +538,7 @@ class TestLocalProviderDecorators:
assert "prompt:my_prompt@" not in provider._components
def test_prompt_enabled_false(self):
"""Prompt with enabled=False should add an Enabled transform."""
"""Prompt with enabled=False should add a Visibility transform."""
provider = LocalProvider()
@provider.prompt(enabled=False)
@ -540,10 +546,12 @@ class TestLocalProviderDecorators:
return "should be disabled"
assert "prompt:disabled_prompt@" in provider._components
# enabled=False adds an Enabled transform to disable the prompt
from fastmcp.server.transforms.enabled import Enabled
# enabled=False adds a Visibility transform to disable the prompt
from fastmcp.server.transforms.visibility import Visibility
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
enabled_transforms = [
t for t in provider.transforms if isinstance(t, Visibility)
]
assert len(enabled_transforms) == 1
assert enabled_transforms[0]._enabled is False
assert enabled_transforms[0].keys == {"prompt:disabled_prompt@"}

View file

@ -229,7 +229,7 @@ class TestSessionVisibility:
assert not any(t.name == "old_tool" for t in tools)
async def test_clear_visibility_rules(self):
"""Test that reset_components removes all session rules."""
"""Test that reset_visibility removes all session rules."""
from fastmcp import Client
mcp = FastMCP("test")
@ -245,7 +245,7 @@ class TestSessionVisibility:
@mcp.tool
async def clear_rules(ctx: Context) -> str:
await ctx.reset_components()
await ctx.reset_visibility()
rules = await ctx._get_visibility_rules()
assert len(rules) == 0
return "cleared"
@ -449,14 +449,14 @@ class TestSessionVisibilityNotifications:
)
async def test_clear_visibility_rules_sends_notifications(self):
"""Test that reset_components sends notifications."""
"""Test that reset_visibility sends notifications."""
from fastmcp import Client
mcp = FastMCP("test")
@mcp.tool
async def clear(ctx: Context) -> str:
await ctx.reset_components()
await ctx.reset_visibility()
return "cleared"
handler = RecordingMessageHandler()

View file

@ -1,8 +1,8 @@
"""Tests for Enabled transform."""
"""Tests for Visibility transform."""
import pytest
from fastmcp.server.transforms.enabled import Enabled, is_enabled
from fastmcp.server.transforms.visibility import Visibility, is_enabled
from fastmcp.tools.tool import Tool
from fastmcp.utilities.versions import VersionSpec
@ -12,43 +12,43 @@ class TestMatching:
def test_empty_criteria_matches_nothing(self):
"""Empty criteria is a safe default - matches nothing."""
t = Enabled(False)
t = Visibility(False)
assert t._matches(Tool(name="anything", parameters={})) is False
def test_match_all_matches_everything(self):
"""match_all=True matches all components."""
t = Enabled(False, match_all=True)
t = Visibility(False, match_all=True)
assert t._matches(Tool(name="anything", parameters={})) is True
def test_match_by_name(self):
"""Matches component by name."""
t = Enabled(False, names={"foo"})
t = Visibility(False, names={"foo"})
assert t._matches(Tool(name="foo", parameters={})) is True
assert t._matches(Tool(name="bar", parameters={})) is False
def test_match_by_version(self):
"""Matches component by version."""
t = Enabled(False, version=VersionSpec(eq="v1"))
t = Visibility(False, version=VersionSpec(eq="v1"))
assert t._matches(Tool(name="foo", version="v1", parameters={})) is True
assert t._matches(Tool(name="foo", version="v2", parameters={})) is False
def test_match_by_version_spec_exact(self):
"""VersionSpec(eq="v1") matches v1 only."""
t = Enabled(False, version=VersionSpec(eq="v1"))
t = Visibility(False, version=VersionSpec(eq="v1"))
assert t._matches(Tool(name="foo", version="v1", parameters={})) is True
assert t._matches(Tool(name="foo", version="v2", parameters={})) is False
assert t._matches(Tool(name="foo", version="v0", parameters={})) is False
def test_match_by_version_spec_gte(self):
"""VersionSpec(gte="v2") matches v2, v3, but not v1."""
t = Enabled(False, version=VersionSpec(gte="v2"))
t = Visibility(False, version=VersionSpec(gte="v2"))
assert t._matches(Tool(name="foo", version="v1", parameters={})) is False
assert t._matches(Tool(name="foo", version="v2", parameters={})) is True
assert t._matches(Tool(name="foo", version="v3", parameters={})) is True
def test_match_by_version_spec_range(self):
"""VersionSpec(gte="v1", lt="v3") matches v1, v2, but not v3."""
t = Enabled(False, version=VersionSpec(gte="v1", lt="v3"))
t = Visibility(False, version=VersionSpec(gte="v1", lt="v3"))
assert t._matches(Tool(name="foo", version="v0", parameters={})) is False
assert t._matches(Tool(name="foo", version="v1", parameters={})) is True
assert t._matches(Tool(name="foo", version="v2", parameters={})) is True
@ -57,27 +57,27 @@ class TestMatching:
def test_unversioned_does_not_match_version_spec(self):
"""Unversioned components (version=None) don't match a VersionSpec."""
t = Enabled(False, version=VersionSpec(eq="v1"))
t = Visibility(False, version=VersionSpec(eq="v1"))
assert t._matches(Tool(name="foo", parameters={})) is False
t2 = Enabled(False, version=VersionSpec(gte="v1"))
t2 = Visibility(False, version=VersionSpec(gte="v1"))
assert t2._matches(Tool(name="foo", parameters={})) is False
def test_match_by_tag(self):
"""Matches if component has any of the specified tags."""
t = Enabled(False, tags=set({"internal", "deprecated"}))
t = Visibility(False, tags=set({"internal", "deprecated"}))
assert t._matches(Tool(name="foo", parameters={}, tags={"internal"})) is True
assert t._matches(Tool(name="foo", parameters={}, tags={"public"})) is False
def test_match_by_component_type(self):
"""Only matches specified component types."""
t = Enabled(False, names={"foo"}, components={"prompt"})
t = Visibility(False, names={"foo"}, components={"prompt"})
# Tool has key "tool:foo@", not "prompt:foo@"
assert t._matches(Tool(name="foo", parameters={})) is False
def test_all_criteria_must_match(self):
"""Multiple criteria use AND logic - all must match."""
t = Enabled(
t = Visibility(
False,
names={"foo"},
version=VersionSpec(eq="v1"),
@ -96,26 +96,26 @@ class TestMatching:
class TestMarking:
"""Test enabled state marking."""
"""Test visibility state marking."""
def test_disable_marks_as_disabled(self):
"""Enabled(False, ...) marks matching components as disabled."""
"""Visibility(False, ...) marks matching components as disabled."""
tool = Tool(name="foo", parameters={})
Enabled(False, names={"foo"})._mark_component(tool)
Visibility(False, names={"foo"})._mark_component(tool)
assert is_enabled(tool) is False
def test_enable_marks_as_enabled(self):
"""Enabled(True, ...) marks matching components as enabled."""
"""Visibility(True, ...) marks matching components as enabled."""
tool = Tool(name="foo", parameters={})
Enabled(True, names={"foo"})._mark_component(tool)
Visibility(True, names={"foo"})._mark_component(tool)
assert is_enabled(tool) is True
assert tool.meta is not None
assert tool.meta["fastmcp"]["_internal"]["enabled"] is True
assert tool.meta["fastmcp"]["_internal"]["visibility"] is True
def test_non_matching_unchanged(self):
"""Non-matching components are not modified."""
tool = Tool(name="bar", parameters={})
Enabled(False, names={"foo"})._mark_component(tool)
Visibility(False, names={"foo"})._mark_component(tool)
# No _internal key added
assert tool.meta is None or "_internal" not in tool.meta.get("fastmcp", {})
assert is_enabled(tool) is True
@ -123,13 +123,13 @@ class TestMarking:
def test_mutates_in_place(self):
"""Marking mutates the component in place."""
tool = Tool(name="foo", parameters={})
result = Enabled(False, names={"foo"})._mark_component(tool)
result = Visibility(False, names={"foo"})._mark_component(tool)
assert result is tool
def test_disable_all(self):
"""match_all=True disables all components."""
tool = Tool(name="anything", parameters={})
Enabled(False, match_all=True)._mark_component(tool)
Visibility(False, match_all=True)._mark_component(tool)
assert is_enabled(tool) is False
@ -139,19 +139,19 @@ class TestOverride:
def test_enable_overrides_disable(self):
"""An enable after disable results in enabled."""
tool = Tool(name="foo", parameters={})
Enabled(False, names={"foo"})._mark_component(tool)
Visibility(False, names={"foo"})._mark_component(tool)
assert is_enabled(tool) is False
Enabled(True, names={"foo"})._mark_component(tool)
Visibility(True, names={"foo"})._mark_component(tool)
assert is_enabled(tool) is True
def test_disable_overrides_enable(self):
"""A disable after enable results in disabled."""
tool = Tool(name="foo", parameters={})
Enabled(True, names={"foo"})._mark_component(tool)
Visibility(True, names={"foo"})._mark_component(tool)
assert is_enabled(tool) is True
Enabled(False, names={"foo"})._mark_component(tool)
Visibility(False, names={"foo"})._mark_component(tool)
assert is_enabled(tool) is False
@ -169,7 +169,7 @@ class TestHelperFunctions:
Tool(name="enabled", parameters={}),
Tool(name="disabled", parameters={}),
]
Enabled(False, names={"disabled"})._mark_component(tools[1])
Visibility(False, names={"disabled"})._mark_component(tools[1])
visible = [t for t in tools if is_enabled(t)]
assert [t.name for t in visible] == ["enabled"]
@ -181,7 +181,7 @@ class TestMetadata:
def test_internal_metadata_stripped_by_get_meta(self):
"""Internal metadata is stripped when calling get_meta()."""
tool = Tool(name="foo", parameters={})
Enabled(True, names={"foo"})._mark_component(tool)
Visibility(True, names={"foo"})._mark_component(tool)
# Raw meta has _internal
assert tool.meta is not None
@ -194,7 +194,7 @@ class TestMetadata:
def test_user_metadata_preserved(self):
"""User-provided metadata is not affected."""
tool = Tool(name="foo", parameters={}, meta={"custom": "value"})
marked = Enabled(False, names={"foo"})._mark_component(tool)
marked = Visibility(False, names={"foo"})._mark_component(tool)
assert marked.meta is not None
assert marked.meta["custom"] == "value"
@ -205,24 +205,24 @@ class TestRepr:
def test_repr_disable(self):
"""Repr shows disable action and criteria."""
t = Enabled(False, names={"foo"})
t = Visibility(False, names={"foo"})
r = repr(t)
assert "disable" in r
assert "foo" in r
def test_repr_enable(self):
"""Repr shows enable action."""
t = Enabled(True, names={"foo"})
t = Visibility(True, names={"foo"})
assert "enable" in repr(t)
def test_repr_match_all(self):
"""Repr shows match_all."""
t = Enabled(False, match_all=True)
t = Visibility(False, match_all=True)
assert "match_all=True" in repr(t)
class TestTransformChain:
"""Test Enabled in async transform chains."""
"""Test Visibility in async transform chains."""
@pytest.fixture
def tools(self):
@ -234,7 +234,7 @@ class TestTransformChain:
async def test_list_tools_marks_matching(self, tools):
"""list_tools applies marks to matching components."""
disable_internal = Enabled(False, tags=set({"internal"}))
disable_internal = Visibility(False, tags=set({"internal"}))
result = await disable_internal.list_tools(tools)
@ -245,8 +245,8 @@ class TestTransformChain:
async def test_later_transform_overrides(self, tools):
"""Later transforms in chain override earlier ones."""
disable_internal = Enabled(False, tags=set({"internal"}))
enable_safe = Enabled(True, tags=set({"safe"}))
disable_internal = Visibility(False, tags=set({"internal"}))
enable_safe = Visibility(True, tags=set({"safe"}))
# Apply transforms sequentially
after_disable = await disable_internal.list_tools(tools)
@ -260,8 +260,8 @@ class TestTransformChain:
async def test_allowlist_pattern(self, tools):
"""Disable all, then enable specific = allowlist."""
disable_all = Enabled(False, match_all=True)
enable_public = Enabled(True, tags=set({"public"}))
disable_all = Visibility(False, match_all=True)
enable_public = Visibility(True, tags=set({"public"}))
# Apply transforms sequentially
after_disable = await disable_all.list_tools(tools)