---
schema: rendered-html/v1
title: "Component Visibility - FastMCP"
source_url: https://gofastmcp.com/servers/visibility
generated_at: 2026-06-11T19:38:04.738128+00:00
---




[FastMCP home page![light logo](images/gofastmcp__com__servers__visibility__wordmark.avif)![dark logo](images/gofastmcp__com__servers__visibility__wordmark-white.avif)](gofastmcp__com.md)







Working with Tools

Component Visibility









  - [Overview](gofastmcp__com__servers__transforms__transforms.md)
  - [Tool Transformation](gofastmcp__com__servers__transforms__tool-transformation.md)
  - [Code Mode

    NEW](gofastmcp__com__servers__transforms__code-mode.md)
  - [Tool Search

    NEW](gofastmcp__com__servers__transforms__tool-search.md)
  - [Namespace](gofastmcp__com__servers__transforms__namespace.md)
  - [Visibility

    NEW](/servers/visibility)
  - [Resources as Tools

    NEW](gofastmcp__com__servers__transforms__resources-as-tools.md)
  - [Prompts as Tools

    NEW](gofastmcp__com__servers__transforms__prompts-as-tools.md)
  - [Tool Fingerprinting](gofastmcp__com__servers__tool-fingerprinting.md)














- [Component Visibility](#component-visibility)
  - [Disabling Components](#disabling-components)
  - [Enabling Components](#enabling-components)
- [Keys and Tags](#keys-and-tags)
  - [Component Keys](#component-keys)
  - [Tags](#tags)
  - [Combining Keys and Tags](#combining-keys-and-tags)
- [Allowlist Mode](#allowlist-mode)
  - [Allowlist Behavior](#allowlist-behavior)
  - [Ordering and Overrides](#ordering-and-overrides)
- [Server vs Provider](#server-vs-provider)
  - [Server-Level](#server-level)
  - [Provider-Level](#provider-level)
  - [Layered Transforms](#layered-transforms)
- [Per-Session Visibility](#per-session-visibility)
  - [How Session Rules Work](#how-session-rules-work)
  - [Filter Criteria](#filter-criteria)
  - [Automatic Notifications](#automatic-notifications)
  - [Namespace Activation Pattern](#namespace-activation-pattern)
  - [Method Reference](#method-reference)
- [Client Notifications](#client-notifications)
- [Filtering Logic](#filtering-logic)
- [The Visibility Transform](#the-visibility-transform)

Working with Tools

# Component Visibility


Control which components are available to clients


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) Component Visibility

Every FastMCP server provides `enable()` and `disable()` methods for controlling component availability.

### [​](#disabling-components) Disabling Components

The `disable()` method marks components as disabled. Disabled components are filtered out from all client queries.


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"

def get_status() -> str:
    """Get system status."""
    return "OK"

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

# Clients only see: get_status

### [​](#enabling-components) Enabling Components

The `enable()` method re-enables previously disabled components.

# Re-enable admin tools
mcp.enable(tags={"admin"})

# Clients now see all three tools

## [​](#keys-and-tags) Keys and Tags

Visibility filtering works with two identifiers: keys (for specific components) and tags (for groups).

### [​](#component-keys) 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.

# 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

Tags group components for bulk operations. Define tags when creating components, then filter by them.


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) Combining Keys and Tags

You can specify both keys and tags in a single call. The filters combine additively.

# Disable specific tools AND all dangerous-tagged components
mcp.disable(keys={"tool:debug_info"}, tags={"dangerous"})

## [​](#allowlist-mode) Allowlist Mode

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.


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"

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) Allowlist Behavior

When you call `enable(only=True)`:

1. Default visibility state switches to “disabled”
2. Previous allowlists are cleared
3. Only specified keys/tags become enabled

# 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) Ordering and Overrides

Later `enable()` and `disable()` calls override earlier ones. This lets you create broad rules with specific exceptions.

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) Server vs Provider

Visibility state operates at two levels: the server and individual providers.

### [​](#server-level) Server-Level

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.


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) Provider-Level

Each provider can add its own visibility transforms. These run before server-level transforms, so the server can override provider-level disables.

from fastmcp.server.providers import LocalProvider

# Create provider with visibility 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) Layered Transforms

Provider transforms run first, then server transforms. Later transforms override earlier ones, so the server has final say.

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)

## [​](#per-session-visibility) Per-Session Visibility

Server-level visibility changes affect all connected clients simultaneously. When you need different clients to see different components, use per-session visibility instead.
Session visibility lets individual sessions customize their view of available components. When a tool calls `ctx.enable_components()` or `ctx.disable_components()`, those rules apply only to the current session. Other sessions continue to see the global defaults. This enables patterns like progressive disclosure, role-based access, and on-demand feature activation.

from fastmcp.server.context import Context

mcp = FastMCP("Session-Aware Server")

@mcp.tool(tags={"premium"})
def premium_analysis(data: str) -> str:
    """Advanced analysis available to premium users."""
    return f"Premium analysis of: {data}"

async def unlock_premium(ctx: Context) -> str:
    """Unlock premium features for this session."""
    await ctx.enable_components(tags={"premium"})
    return "Premium features unlocked"

async def reset_features(ctx: Context) -> str:
    """Reset to default feature set."""
    await ctx.reset_visibility()
    return "Features reset to defaults"

# Premium tools are disabled globally by default
mcp.disable(tags={"premium"})

All sessions start with `premium_analysis` hidden. When a session calls `unlock_premium`, that session gains access to premium tools while other sessions remain unaffected. Calling `reset_features` returns the session to the global defaults.

### [​](#how-session-rules-work) How Session Rules Work

Session rules override global transforms. When listing components, FastMCP first applies global enable/disable rules, then applies session-specific rules on top. Rules within a session accumulate, and later rules override earlier ones for the same component.

async def customize_session(ctx: Context) -> str:
    # Enable finance tools for this session
    await ctx.enable_components(tags={"finance"})

    # Also enable admin tools
    await ctx.enable_components(tags={"admin"})

    # Later: disable a specific admin tool
    await ctx.disable_components(names={"dangerous_admin_tool"})

    return "Session customized"

Each call adds a rule to the session. The `dangerous_admin_tool` ends up disabled because its disable rule was added after the admin enable rule.

### [​](#filter-criteria) Filter Criteria

The session visibility methods accept the same filter criteria as `server.enable()` and `server.disable()`:

| Parameter | Description |
| --- | --- |
| `names` | Component names or URIs to match |
| `keys` | Component keys (e.g., `{"tool:my_tool"}`) |
| `tags` | Tags to match (component must have at least one) |
| `version` | Version specification to match |
| `components` | Component types (`{"tool"}`, `{"resource"}`, `{"prompt"}`, `{"template"}`) |
| `match_all` | If `True`, matches all components regardless of other criteria |

from fastmcp.utilities.versions import VersionSpec

async def enable_recent_tools(ctx: Context) -> str:
    """Enable only tools from version 2.0.0 or later."""
    await ctx.enable_components(
        version=VersionSpec(gte="2.0.0"),
        components={"tool"}
    return "Recent tools enabled"

### [​](#automatic-notifications) Automatic Notifications

When session visibility changes, FastMCP automatically sends notifications to that session. Clients receive `ToolListChangedNotification`, `ResourceListChangedNotification`, and `PromptListChangedNotification` so they can refresh their component lists. These notifications go only to the affected session.
When you specify the `components` parameter, FastMCP optimizes by sending only the relevant notifications:

# Only sends ToolListChangedNotification
await ctx.enable_components(tags={"finance"}, components={"tool"})

# Sends all three notifications (no components filter)
await ctx.enable_components(tags={"finance"})

### [​](#namespace-activation-pattern) Namespace Activation Pattern

A common pattern organizes tools into namespaces using tag prefixes, disables them globally, then provides activation tools that unlock namespaces on demand:

from fastmcp.server.context import Context

server = FastMCP("Multi-Domain Assistant")

# Finance namespace
@server.tool(tags={"namespace:finance"})
def analyze_portfolio(symbols: list[str]) -> str:
    return f"Analysis for: {', '.join(symbols)}"

@server.tool(tags={"namespace:finance"})
def get_market_data(symbol: str) -> dict:
    return {"symbol": symbol, "price": 150.25}

# Admin namespace
@server.tool(tags={"namespace:admin"})
def list_users() -> list[str]:
    return ["alice", "bob", "charlie"]

# Activation tools - always visible
@server.tool
async def activate_finance(ctx: Context) -> str:
    await ctx.enable_components(tags={"namespace:finance"})
    return "Finance tools activated"

@server.tool
async def activate_admin(ctx: Context) -> str:
    await ctx.enable_components(tags={"namespace:admin"})
    return "Admin tools activated"

@server.tool
async def deactivate_all(ctx: Context) -> str:
    await ctx.reset_visibility()
    return "All namespaces deactivated"

# Disable namespace tools globally
server.disable(tags={"namespace:finance", "namespace:admin"})

Sessions start seeing only the activation tools. Calling `activate_finance` reveals finance tools for that session only. Multiple namespaces can be activated independently, and `deactivate_all` returns to the initial state.

### [​](#method-reference) Method Reference

- **`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_visibility() -> None`**: Clear all session rules, returning to global defaults

## [​](#client-notifications) Client Notifications

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.

# This automatically notifies clients
mcp.disable(tags={"maintenance"})

# Clients receive: tools/list_changed, resources/list_changed, etc.

## [​](#filtering-logic) Filtering Logic

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.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-visibility-transform) The Visibility Transform

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.

from fastmcp.server.transforms import Visibility

mcp = FastMCP("Server")

# Using the convenience method (recommended)
mcp.disable(names={"secret_tool"})

# Equivalent to:
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.

[Namespace Transform

Previous](gofastmcp__com__servers__transforms__namespace.md)[Resources as Tools

Next](gofastmcp__com__servers__transforms__resources-as-tools.md)






