--- title: Component Visibility sidebarTitle: Component Visibility description: Control which components are available to clients icon: toggle-on --- import { VersionBadge } from '/snippets/version-badge.mdx' 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" ``` Dynamic enabled state changes affect all connected clients. For per-user filtering, consider using separate server instances or implementing authorization in the tools themselves. ## 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.