Refactor visibility to mark-based enabled system

Rename Visibility to Enabled, collapse VisibilityRule into the transform,
and move enabled filtering from Provider to Server level so server-level
transforms can override provider-level disables.
This commit is contained in:
Jeremiah Lowin 2026-01-18 14:36:33 -05:00
commit 50ba6ea5a4
41 changed files with 1378 additions and 2043 deletions

View file

@ -0,0 +1,160 @@
---
title: enabled
sidebarTitle: enabled
---
# `fastmcp.server.transforms.enabled`
Enabled transform for marking component enabled state.
This module provides the `Enabled` class which marks components with enabled/disabled
state using metadata. Multiple Enabled transforms can be stacked - later transforms
override earlier ones. Final filtering happens at the Provider level.
## Classes
### `Enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Sets enabled state on matching components.
Does NOT filter inline - just marks components with enabled state.
Later transforms in the chain can override earlier marks.
Final filtering happens at the Provider level after all transforms run.
Filtering logic (blocklist wins over allowlist):
1. If component key is in _disabled_keys -> DISABLED
2. If any component tag is in _disabled_tags -> DISABLED
3. If _default_enabled is False and component not in allowlist -> DISABLED
4. Otherwise -> ENABLED
Example usage:
```python
from fastmcp.server.transforms import Enabled
# Disable components tagged "internal"
Enabled(False, tags=frozenset({"internal"}))
# Re-enable specific tool (override earlier disable)
Enabled(True, name="safe_tool")
# Allowlist via composition:
Enabled(False, match_all=True) # disable everything
Enabled(True, tags=frozenset({"public"})) # enable public
```
**Methods:**
#### `__init__` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L61" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
__init__(self, enabled: bool, *, name: str | None = None, version: str | None = None, tags: frozenset[str] | None = None, components: frozenset[str] | None = None, match_all: bool = False) -> None
```
Initialize an enabled marker.
**Args:**
- `enabled`: If True, mark matching as enabled; if False, mark as disabled.
- `name`: Component name to match.
- `version`: Component version to match.
- `tags`: Tags to match (component must have at least one).
- `components`: Component types to match (e.g., frozenset({"tool", "prompt"})).
- `match_all`: If True, matches all components regardless of other criteria.
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_tools(self, call_next: ListToolsNext) -> Sequence[Tool]
```
Mark tools by enabled state.
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L187" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_tool(self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None) -> Tool | None
```
Mark tool if found.
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L200" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_resources(self, call_next: ListResourcesNext) -> Sequence[Resource]
```
Mark resources by enabled state.
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_resource(self, uri: str, call_next: GetResourceNext, *, version: VersionSpec | None = None) -> 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#L222" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_resource_templates(self, call_next: ListResourceTemplatesNext) -> Sequence[ResourceTemplate]
```
Mark resource templates by enabled state.
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_resource_template(self, uri: str, call_next: GetResourceTemplateNext, *, version: VersionSpec | None = None) -> ResourceTemplate | None
```
Mark resource template if found.
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L246" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_prompts(self, call_next: ListPromptsNext) -> Sequence[Prompt]
```
Mark prompts by enabled state.
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L251" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_prompt(self, name: str, call_next: GetPromptNext, *, version: VersionSpec | None = None) -> Prompt | None
```
Mark prompt if found.
## Functions
### `is_enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L261" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
is_enabled(component: FastMCPComponent) -> bool
```
Check if component is enabled.
Returns True if:
- No enabled mark exists (default is enabled)
- Enabled mark is True
Returns False if enabled mark is False.
**Args:**
- `component`: Component to check.
**Returns:**
True if component should be enabled/visible to clients.

View file

@ -1,158 +0,0 @@
---
title: visibility
sidebarTitle: visibility
---
# `fastmcp.server.transforms.visibility`
Visibility transform for filtering components based on enable/disable settings.
This module provides the `Visibility` class which manages component visibility
with blocklist and allowlist support. Components can be hidden by key or tag,
and the visibility state is mutable - changes take effect on subsequent queries.
## Classes
### `Visibility` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L43" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Filters components based on visibility settings.
Manages blocklist and allowlist logic for controlling component visibility.
Both servers and providers use this class. Visibility is hierarchical: if a
component is hidden at any level (provider or server), it's hidden to the client.
Filtering logic (blocklist wins over allowlist):
1. If component key is in _disabled_keys → HIDDEN
2. If any component tag is in _disabled_tags → HIDDEN
3. If _default_enabled is False and component not in allowlist → HIDDEN
4. Otherwise → VISIBLE
The `only=True` flag on enable() switches to allowlist mode:
- Sets _default_enabled = False
- Clears existing allowlists
- Adds specified keys/tags to allowlist
**Methods:**
#### `disable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
disable(self) -> None
```
Add to blocklist (hide components).
**Args:**
- `keys`: Component keys to hide (e.g., "tool\:my_tool@", "resource\:file\://x@")
- `tags`: Tags to hide - any component with these tags will be hidden
#### `enable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L148" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
enable(self) -> None
```
Remove from blocklist, or set allowlist with only=True.
**Args:**
- `keys`: Component keys to show
- `tags`: Tags to show
- `only`: If True, switches to allowlist mode - ONLY show these keys/tags.
This sets default visibility to False, clears existing allowlists,
and adds the specified keys/tags to the allowlist.
#### `reset` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L202" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
reset(self) -> None
```
Reset to default state (everything enabled, no filters).
#### `is_enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L221" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
is_enabled(self, component: FastMCPComponent) -> bool
```
Check if component is enabled. Blocklist wins over allowlist.
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L241" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_tools(self, call_next: ListToolsNext) -> Sequence[Tool]
```
Filter tools by visibility.
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L246" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_tool(self, name: str, call_next: GetToolNext) -> Tool | None
```
Get tool if enabled, None otherwise.
#### `list_resources` <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
list_resources(self, call_next: ListResourcesNext) -> Sequence[Resource]
```
Filter resources by visibility.
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L264" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_resource(self, uri: str, call_next: GetResourceNext) -> Resource | None
```
Get resource if enabled, None otherwise.
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L281" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_resource_templates(self, call_next: ListResourceTemplatesNext) -> Sequence[ResourceTemplate]
```
Filter resource templates by visibility.
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L288" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_resource_template(self, uri: str, call_next: GetResourceTemplateNext) -> ResourceTemplate | None
```
Get resource template if enabled, None otherwise.
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L305" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
list_prompts(self, call_next: ListPromptsNext) -> Sequence[Prompt]
```
Filter prompts by visibility.
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L310" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_prompt(self, name: str, call_next: GetPromptNext) -> Prompt | None
```
Get prompt if enabled, None otherwise.