fastmcp/docs/servers/enabled.mdx

334 lines
9.2 KiB
Text

---
title: Component Visibility
sidebarTitle: Component Visibility
description: Control which components are available to clients
icon: toggle-on
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.0.0" />
Components can be dynamically enabled or disabled at runtime. A disabled tool disappears from listings and cannot be called. This enables runtime access control, feature flags, and context-aware component exposure.
## Component Visibility
Every FastMCP server provides `enable()` and `disable()` methods for controlling component availability.
### Disabling Components
The `disable()` method marks components as disabled. Disabled components are filtered out from all client queries.
```python
from fastmcp import FastMCP
mcp = FastMCP("Server")
@mcp.tool(tags={"admin"})
def delete_everything() -> str:
"""Delete all data."""
return "Deleted"
@mcp.tool(tags={"admin"})
def reset_system() -> str:
"""Reset the system."""
return "Reset"
@mcp.tool
def get_status() -> str:
"""Get system status."""
return "OK"
# Disable admin tools
mcp.disable(tags={"admin"})
# Clients only see: get_status
```
### Enabling Components
The `enable()` method re-enables previously disabled components.
```python
# Re-enable admin tools
mcp.enable(tags={"admin"})
# Clients now see all three tools
```
## Keys and Tags
Enabled filtering works with two identifiers: keys (for specific components) and tags (for groups).
### Component Keys
Every component has a unique key in the format `{type}:{identifier}`.
| Component | Key Format | Example |
|-----------|------------|---------|
| Tool | `tool:{name}` | `tool:delete_everything` |
| Resource | `resource:{uri}` | `resource:data://config` |
| Template | `template:{uri}` | `template:file://{path}` |
| Prompt | `prompt:{name}` | `prompt:analyze` |
Use keys to target specific components.
```python
# Disable a specific tool
mcp.disable(keys={"tool:delete_everything"})
# Disable multiple specific components
mcp.disable(keys={"tool:reset_system", "resource:data://secrets"})
```
### Tags
Tags group components for bulk operations. Define tags when creating components, then filter by them.
```python
from fastmcp import FastMCP
mcp = FastMCP("Server")
@mcp.tool(tags={"public", "read"})
def get_data() -> str:
return "data"
@mcp.tool(tags={"admin", "write"})
def set_data(value: str) -> str:
return f"Set: {value}"
@mcp.tool(tags={"admin", "dangerous"})
def delete_data() -> str:
return "Deleted"
# Disable all admin tools
mcp.disable(tags={"admin"})
# Disable all dangerous tools (some overlap with admin)
mcp.disable(tags={"dangerous"})
```
A component is disabled if it has **any** of the disabled tags. The component doesn't need all the tags; one match is enough.
### Combining Keys and Tags
You can specify both keys and tags in a single call. The filters combine additively.
```python
# Disable specific tools AND all dangerous-tagged components
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.
```python
from fastmcp import FastMCP
mcp = FastMCP("Server")
@mcp.tool(tags={"safe"})
def read_only_operation() -> str:
return "Read"
@mcp.tool(tags={"safe"})
def list_items() -> list[str]:
return ["a", "b", "c"]
@mcp.tool(tags={"dangerous"})
def delete_all() -> str:
return "Deleted"
@mcp.tool
def untagged_tool() -> str:
return "Untagged"
# Only enable safe tools - everything else is disabled
mcp.enable(tags={"safe"}, only=True)
# Clients see: read_only_operation, list_items
# Disabled: delete_all, untagged_tool
```
Allowlist mode is useful for restrictive environments where you want to explicitly opt-in components rather than opt-out.
### Allowlist Behavior
When you call `enable(only=True)`:
1. Default enabled state switches to "disabled"
2. Previous allowlists are cleared
3. Only specified keys/tags become enabled
```python
# Start fresh - only enable these specific tools
mcp.enable(keys={"tool:safe_read", "tool:safe_write"}, only=True)
# Later, switch to a different allowlist
mcp.enable(tags={"production"}, only=True)
```
### Ordering and Overrides
Later `enable()` and `disable()` calls override earlier ones. This lets you create broad rules with specific exceptions.
```python
mcp.enable(tags={"api"}, only=True) # Allow all api-tagged
mcp.disable(keys={"tool:api_admin"}) # Later disable overrides for this tool
# api_admin is disabled because the later disable() overrides the allowlist
```
You can always re-enable something that was disabled by adding another `enable()` call after it.
## Server vs Provider
Enabled 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.
```python
from fastmcp import FastMCP
main = FastMCP("Main")
main.mount(sub_server, namespace="api")
@main.tool(tags={"internal"})
def local_debug() -> str:
return "Debug"
# Disable internal tools from ALL sources
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.
```python
from fastmcp import FastMCP
from fastmcp.server.providers import LocalProvider
# Create provider with enabled control
admin_tools = LocalProvider()
@admin_tools.tool(tags={"admin"})
def admin_action() -> str:
return "Admin"
@admin_tools.tool
def regular_action() -> str:
return "Regular"
# Disable at provider level
admin_tools.disable(tags={"admin"})
# Server can override if needed
mcp = FastMCP("Server", providers=[admin_tools])
mcp.enable(names={"admin_action"}) # Re-enables despite provider disable
```
Provider-level transforms are useful for setting default visibility that servers can selectively override.
### Layered Transforms
Provider transforms run first, then server transforms. Later transforms override earlier ones, so the server has final say.
```python
from fastmcp import FastMCP
from fastmcp.server.providers import LocalProvider
provider = LocalProvider()
@provider.tool(tags={"feature", "beta"})
def new_feature() -> str:
return "New"
# Provider enables feature-tagged
provider.enable(tags={"feature"}, only=True)
# Server disables beta-tagged (runs after provider)
mcp = FastMCP("Server", providers=[provider])
mcp.disable(tags={"beta"})
# new_feature is disabled (server's later disable overrides provider's enable)
```
## Dynamic Changes
Enabled state changes take effect immediately. You can adjust during request handling based on context.
```python
from fastmcp import FastMCP
from fastmcp.server import Context
mcp = FastMCP("Server")
@mcp.tool(tags={"admin"})
def admin_action() -> str:
return "Admin action performed"
@mcp.tool
def check_permissions(ctx: Context) -> str:
"""Check if admin tools should be available."""
user = ctx.request_context.get_user()
if user and user.is_admin:
mcp.enable(tags={"admin"})
return "Admin tools enabled"
else:
mcp.disable(tags={"admin"})
return "Admin tools disabled"
```
<Warning>
Dynamic enabled state changes affect all connected clients. For per-user filtering, consider using separate server instances or implementing authorization in the tools themselves.
</Warning>
## 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.
This happens automatically. You don't need to trigger notifications manually.
```python
# This automatically notifies clients
mcp.disable(tags={"maintenance"})
# Clients receive: tools/list_changed, resources/list_changed, etc.
```
## Filtering Logic
Understanding the filtering logic helps when debugging enabled 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
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
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.
```python
from fastmcp import FastMCP
from fastmcp.server.transforms import Enabled
mcp = FastMCP("Server")
# Using the convenience method (recommended)
mcp.disable(names={"secret_tool"})
# Equivalent to:
mcp.add_transform(Enabled(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.