Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions docs/development/upgrade-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ mcp.add_transform(ToolTransform({
```
</CodeGroup>

`remove_tool_transformation()` is deprecated with no replacement - transforms are immutable once added. Use `server.disable(keys=[...])` to hide tools dynamically.
`remove_tool_transformation()` is deprecated with no replacement - transforms are immutable once added. Use `server.disable(names={...}, components=["tool"])` to hide tools dynamically.

### FastMCP.as_proxy() Deprecated

Expand Down Expand Up @@ -185,7 +185,7 @@ main.mount(subserver, namespace="api")

### Component Enable/Disable

The `enabled` field and `enable()`/`disable()` methods have been removed from component objects. Use server or provider methods instead:
The `enable()`/`disable()` methods have moved from component objects to the server and provider level:

<CodeGroup>
```python Before
Expand All @@ -195,16 +195,26 @@ tool.enable()
```

```python After
server.disable(keys=["tool:my_tool@"])
server.enable(keys=["tool:my_tool@"])
server.disable(names={"my_tool"}, components=["tool"])
server.enable(names={"my_tool"}, components=["tool"])
```
</CodeGroup>

Components describe capabilities; servers and providers control availability. This ensures mutations work correctly even when components pass through transforming providers.
Components describe capabilities; servers and providers control availability. This ensures enabled state works correctly even when components pass through transforming providers.

**Override semantics:**

Multiple `enable()`/`disable()` calls are additive. Later calls override earlier ones for matching components:

```python
server.disable(tags={"internal"}) # Hide all internal
server.enable(names={"safe_tool"}) # Show safe_tool (overrides the disable)
# Result: safe_tool is visible, other internal tools are hidden
```

**Allowlist mode:**

Use `only=True` to restrict visibility to specific components:
Use `only=True` to restrict access to specific components:

```python
# Show ONLY tools with "public" tag
Expand All @@ -215,14 +225,29 @@ server.enable(tags={"public"}, only=True)

These init parameters emit deprecation warnings. Use the new methods instead:

```python
# Before
<CodeGroup>
```python Before
mcp = FastMCP("server", exclude_tags={"internal"})
```

# After
```python After
mcp = FastMCP("server")
mcp.disable(tags={"internal"})
```
</CodeGroup>

**No automatic notifications:**

Component `enable()`/`disable()` in v2 sent `ToolListChangedNotification` automatically. The new server-level methods don't - they're treated as startup configuration. To notify clients of enabled state changes at runtime, send notifications explicitly:

```python
import mcp.types

@server.tool
async def hide_admin_tools(ctx: Context):
ctx.fastmcp.disable(tags={"admin"})
await ctx.send_notification(mcp.types.ToolListChangedNotification())
```

### Component Lookup Method Parameter Names

Expand Down
29 changes: 19 additions & 10 deletions docs/development/v3-notes/v3-features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Provider:

Providers support:
- **Lifecycle management**: `async def lifespan()` for setup/teardown
- **Visibility control**: `enable()` / `disable()` with keys, tags, and allowlist mode
- **Enabled control**: `enable()` / `disable()` with name, version, tags, components, and allowlist mode
- **Transform stacking**: `provider.add_transform(Namespace(...))`, `provider.add_transform(ToolTransform(...))`

### LocalProvider
Expand Down Expand Up @@ -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)
- `Visibility` - filters components by key or tag (backs `enable()`/`disable()` API)
- `Enabled` - sets enabled state on components by key or tag (backs `enable()`/`disable()` API)
- `VersionFilter` - filters components by version range (`version_gte`, `version_lt`)

```python
Expand Down Expand Up @@ -147,7 +147,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/visibility.mdx`
Documentation: `docs/servers/providers/transforms.mdx`, `docs/servers/enabled.mdx`

---

Expand Down Expand Up @@ -182,27 +182,36 @@ Documentation: `docs/servers/context.mdx`

---

## Visibility System
## Enabled System

Components can be dynamically enabled/disabled at runtime using the visibility system ([#2708](https://github.com/jlowin/fastmcp/pull/2708)).
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.

```python
mcp = FastMCP("Server")

# Disable specific components (keys include @ version suffix)
mcp.disable(keys=["tool:dangerous_tool@"])
# Disable by name and component type
mcp.disable(names={"dangerous_tool"}, components=["tool"])

# Disable by tag
mcp.disable(tags={"admin"})

# Allowlist mode - only show these
mcp.enable(keys=["tool:safe_tool@"], only=True)
# Disable by version
mcp.disable(names={"old_tool"}, version="1.0", components=["tool"])

# Allowlist mode - only show components with these tags
mcp.enable(tags={"public"}, only=True)

# Enable overrides earlier disable (later transform wins)
mcp.disable(tags={"internal"})
mcp.enable(names={"safe_tool"}) # safe_tool is visible despite internal tag
```

Works at both server and provider level. Supports:
- **Blocklist mode** (default): All components visible except explicitly disabled
- **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

---

Expand Down Expand Up @@ -814,7 +823,7 @@ tool = await server.get_tool("my_tool")
tool.disable()

# v3.0
server.disable(keys=["tool:my_tool@"])
server.disable(names={"my_tool"}, components=["tool"])
```

### Component Lookup Methods
Expand Down
4 changes: 2 additions & 2 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
"servers/tasks",
"servers/telemetry",
"servers/versioning",
"servers/visibility"
"servers/enabled"
]
},
{
Expand Down Expand Up @@ -497,7 +497,7 @@
"python-sdk/fastmcp-server-transforms-namespace",
"python-sdk/fastmcp-server-transforms-tool_transform",
"python-sdk/fastmcp-server-transforms-version_filter",
"python-sdk/fastmcp-server-transforms-visibility"
"python-sdk/fastmcp-server-transforms-enabled"
]
}
]
Expand Down
160 changes: 160 additions & 0 deletions docs/python-sdk/fastmcp-server-transforms-enabled.mdx
Original file line number Diff line number Diff line change
@@ -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, names={"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.
Loading