mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Rename Enabled transform to Visibility (#2950)
This commit is contained in:
parent
ba18a716bf
commit
0e4d519c86
24 changed files with 245 additions and 236 deletions
|
|
@ -23,7 +23,7 @@ class Provider:
|
|||
|
||||
Providers support:
|
||||
- **Lifecycle management**: `async def lifespan()` for setup/teardown
|
||||
- **Enabled control**: `enable()` / `disable()` with name, version, tags, components, and allowlist mode
|
||||
- **Visibility control**: `enable()` / `disable()` with name, version, tags, components, and allowlist mode
|
||||
- **Transform stacking**: `provider.add_transform(Namespace(...))`, `provider.add_transform(ToolTransform(...))`
|
||||
|
||||
### LocalProvider
|
||||
|
|
@ -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)
|
||||
- `Enabled` - sets enabled state on components by key or tag (backs `enable()`/`disable()` API)
|
||||
- `Visibility` - sets visibility state on components by key or tag (backs `enable()`/`disable()` API)
|
||||
- `VersionFilter` - filters components by version range (`version_gte`, `version_lt`)
|
||||
- `ResourcesAsTools` - exposes resources as tools for tool-only clients
|
||||
- `PromptsAsTools` - exposes prompts as tools for tool-only clients
|
||||
|
|
@ -149,7 +149,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/enabled.mdx`
|
||||
Documentation: `docs/servers/providers/transforms.mdx`, `docs/servers/visibility.mdx`
|
||||
|
||||
### ResourcesAsTools and PromptsAsTools
|
||||
|
||||
|
|
@ -232,9 +232,9 @@ Documentation: `docs/servers/context.mdx`
|
|||
|
||||
---
|
||||
|
||||
## Enabled System
|
||||
## Visibility System
|
||||
|
||||
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.
|
||||
Components can be enabled/disabled using the visibility system. Each `enable()` or `disable()` call adds a stateless Visibility transform that marks components via internal metadata. Later transforms override earlier ones.
|
||||
|
||||
```python
|
||||
mcp = FastMCP("Server")
|
||||
|
|
@ -261,7 +261,7 @@ Works at both server and provider level. Supports:
|
|||
- **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
|
||||
- **Transform ordering**: Visibility transforms are injected at the point you call them, so component state is known
|
||||
|
||||
### Per-Session Visibility
|
||||
|
||||
|
|
@ -286,7 +286,7 @@ async def unlock_premium(ctx: Context) -> str:
|
|||
@mcp.tool
|
||||
async def reset_features(ctx: Context) -> str:
|
||||
"""Reset to default feature set."""
|
||||
await ctx.reset_components()
|
||||
await ctx.reset_visibility()
|
||||
return "Features reset to defaults"
|
||||
|
||||
# Globally disabled - sessions unlock individually
|
||||
|
|
@ -296,11 +296,11 @@ mcp.disable(tags={"premium"})
|
|||
Session visibility methods:
|
||||
- `await ctx.enable_components(...)`: Enable components for this session
|
||||
- `await ctx.disable_components(...)`: Disable components for this session
|
||||
- `await ctx.reset_components()`: Clear session rules, return to global defaults
|
||||
- `await ctx.reset_visibility()`: Clear session rules, return to global defaults
|
||||
|
||||
Session rules override global transforms. FastMCP automatically sends `ToolListChangedNotification` (and resource/prompt equivalents) to affected sessions when visibility changes.
|
||||
|
||||
Documentation: `docs/servers/enabled.mdx`
|
||||
Documentation: `docs/servers/visibility.mdx`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@
|
|||
"servers/tasks",
|
||||
"servers/telemetry",
|
||||
"servers/versioning",
|
||||
"servers/enabled"
|
||||
"servers/visibility"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -563,7 +563,7 @@
|
|||
"group": "transforms",
|
||||
"pages": [
|
||||
"python-sdk/fastmcp-server-transforms-__init__",
|
||||
"python-sdk/fastmcp-server-transforms-enabled",
|
||||
"python-sdk/fastmcp-server-transforms-visibility",
|
||||
"python-sdk/fastmcp-server-transforms-namespace",
|
||||
"python-sdk/fastmcp-server-transforms-prompts_as_tools",
|
||||
"python-sdk/fastmcp-server-transforms-resources_as_tools",
|
||||
|
|
|
|||
|
|
@ -591,7 +591,7 @@ Enable components matching criteria for this session only.
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -615,7 +615,7 @@ Disable components matching criteria for this session only.
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -629,10 +629,10 @@ ResourceListChangedNotification, and PromptListChangedNotification.
|
|||
- `match_all`: If True, matches all components regardless of other criteria.
|
||||
|
||||
|
||||
#### `reset_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L1073" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `reset_visibility` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L1073" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
reset_components(self) -> None
|
||||
reset_visibility(self) -> None
|
||||
```
|
||||
|
||||
Clear all session visibility rules.
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ get_tool(self, name: str, version: VersionSpec | None = None) -> Tool | None
|
|||
Get tool by transformed name with all transforms applied.
|
||||
|
||||
Note: This method does NOT filter disabled components. The Server
|
||||
(FastMCP) performs enabled filtering after all transforms complete,
|
||||
(FastMCP) performs visibility filtering after all transforms complete,
|
||||
allowing session-level transforms to override provider-level disables.
|
||||
|
||||
**Args:**
|
||||
|
|
@ -152,7 +152,7 @@ get_resource(self, uri: str, version: VersionSpec | None = None) -> Resource | N
|
|||
Get resource by transformed URI with all transforms applied.
|
||||
|
||||
Note: This method does NOT filter disabled components. The Server
|
||||
(FastMCP) performs enabled filtering after all transforms complete.
|
||||
(FastMCP) performs visibility filtering after all transforms complete.
|
||||
|
||||
**Args:**
|
||||
- `uri`: The transformed resource URI to look up.
|
||||
|
|
@ -182,7 +182,7 @@ get_resource_template(self, uri: str, version: VersionSpec | None = None) -> Res
|
|||
Get resource template by transformed URI with all transforms applied.
|
||||
|
||||
Note: This method does NOT filter disabled components. The Server
|
||||
(FastMCP) performs enabled filtering after all transforms complete.
|
||||
(FastMCP) performs visibility filtering after all transforms complete.
|
||||
|
||||
**Args:**
|
||||
- `uri`: The transformed template URI to look up.
|
||||
|
|
@ -212,7 +212,7 @@ get_prompt(self, name: str, version: VersionSpec | None = None) -> Prompt | None
|
|||
Get prompt by transformed name with all transforms applied.
|
||||
|
||||
Note: This method does NOT filter disabled components. The Server
|
||||
(FastMCP) performs enabled filtering after all transforms complete.
|
||||
(FastMCP) performs visibility filtering after all transforms complete.
|
||||
|
||||
**Args:**
|
||||
- `name`: The transformed prompt name to look up.
|
||||
|
|
@ -289,7 +289,7 @@ disable(self) -> Self
|
|||
|
||||
Disable components matching all specified criteria.
|
||||
|
||||
Adds an enabled transform that marks matching components as disabled.
|
||||
Adds a visibility transform that marks matching components as disabled.
|
||||
Components can be re-enabled by calling enable() with matching criteria
|
||||
(the later transform wins).
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ list_tools(self) -> Sequence[Tool]
|
|||
|
||||
List all enabled tools from providers.
|
||||
|
||||
Overrides Provider.list_tools() to add enabled filtering, auth filtering,
|
||||
Overrides Provider.list_tools() to add visibility filtering, auth filtering,
|
||||
and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ get_tool(self, name: str, version: VersionSpec | None = None) -> Tool | None
|
|||
|
||||
Get a tool by name, filtering disabled tools.
|
||||
|
||||
Overrides Provider.get_tool() to add enabled filtering after all
|
||||
Overrides Provider.get_tool() to add visibility filtering after all
|
||||
transforms (including session-level) have been applied. This ensures
|
||||
session transforms can override provider-level disables.
|
||||
|
||||
|
|
@ -224,7 +224,7 @@ list_resources(self) -> Sequence[Resource]
|
|||
|
||||
List all enabled resources from providers.
|
||||
|
||||
Overrides Provider.list_resources() to add enabled filtering, auth filtering,
|
||||
Overrides Provider.list_resources() to add visibility filtering, auth filtering,
|
||||
and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ get_resource(self, uri: str, version: VersionSpec | None = None) -> Resource | N
|
|||
|
||||
Get a resource by URI, filtering disabled resources.
|
||||
|
||||
Overrides Provider.get_resource() to add enabled filtering after all
|
||||
Overrides Provider.get_resource() to add visibility filtering after all
|
||||
transforms (including session-level) have been applied.
|
||||
|
||||
**Args:**
|
||||
|
|
@ -256,7 +256,7 @@ list_resource_templates(self) -> Sequence[ResourceTemplate]
|
|||
|
||||
List all enabled resource templates from providers.
|
||||
|
||||
Overrides Provider.list_resource_templates() to add enabled filtering,
|
||||
Overrides Provider.list_resource_templates() to add visibility filtering,
|
||||
auth filtering, and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
|
||||
|
|
@ -269,7 +269,7 @@ get_resource_template(self, uri: str, version: VersionSpec | None = None) -> Res
|
|||
|
||||
Get a resource template by URI, filtering disabled templates.
|
||||
|
||||
Overrides Provider.get_resource_template() to add enabled filtering after
|
||||
Overrides Provider.get_resource_template() to add visibility filtering after
|
||||
all transforms (including session-level) have been applied.
|
||||
|
||||
**Args:**
|
||||
|
|
@ -288,7 +288,7 @@ list_prompts(self) -> Sequence[Prompt]
|
|||
|
||||
List all enabled prompts from providers.
|
||||
|
||||
Overrides Provider.list_prompts() to add enabled filtering, auth filtering,
|
||||
Overrides Provider.list_prompts() to add visibility filtering, auth filtering,
|
||||
and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
|
||||
|
|
@ -301,7 +301,7 @@ get_prompt(self, name: str, version: VersionSpec | None = None) -> Prompt | None
|
|||
|
||||
Get a prompt by name, filtering disabled prompts.
|
||||
|
||||
Overrides Provider.get_prompt() to add enabled filtering after all
|
||||
Overrides Provider.get_prompt() to add visibility filtering after all
|
||||
transforms (including session-level) have been applied.
|
||||
|
||||
**Args:**
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
---
|
||||
title: enabled
|
||||
sidebarTitle: enabled
|
||||
title: visibility
|
||||
sidebarTitle: visibility
|
||||
---
|
||||
|
||||
# `fastmcp.server.transforms.enabled`
|
||||
# `fastmcp.server.transforms.visibility`
|
||||
|
||||
|
||||
Enabled transform for marking component enabled state.
|
||||
Visibility transform for marking component visibility state.
|
||||
|
||||
Each Enabled instance marks components via internal metadata. Multiple
|
||||
enabled transforms can be stacked - later transforms override earlier ones.
|
||||
Each Visibility instance marks components via internal metadata. Multiple
|
||||
visibility transforms can be stacked - later transforms override earlier ones.
|
||||
Final filtering happens at the Provider level.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### `is_enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `is_enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_enabled(component: FastMCPComponent) -> bool
|
||||
|
|
@ -25,10 +25,10 @@ is_enabled(component: FastMCPComponent) -> bool
|
|||
Check if component is enabled.
|
||||
|
||||
Returns True if:
|
||||
- No enabled mark exists (default is enabled)
|
||||
- Enabled mark is True
|
||||
- No visibility mark exists (default is enabled)
|
||||
- Visibility mark is True
|
||||
|
||||
Returns False if enabled mark is False.
|
||||
Returns False if visibility mark is False.
|
||||
|
||||
**Args:**
|
||||
- `component`: Component to check.
|
||||
|
|
@ -37,7 +37,7 @@ Returns False if enabled mark is False.
|
|||
- True if component should be enabled/visible to clients.
|
||||
|
||||
|
||||
### `get_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_visibility_rules(context: Context) -> list[dict[str, Any]]
|
||||
|
|
@ -47,7 +47,7 @@ get_visibility_rules(context: Context) -> list[dict[str, Any]]
|
|||
Load visibility rule dicts from session state.
|
||||
|
||||
|
||||
### `save_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L303" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `save_visibility_rules` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L303" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
save_visibility_rules(context: Context, rules: list[dict[str, Any]]) -> None
|
||||
|
|
@ -64,27 +64,27 @@ If None, sends notifications for all types (safe default).
|
|||
If provided, only sends notifications for specified types.
|
||||
|
||||
|
||||
### `create_enabled_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `create_visibility_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]
|
||||
create_visibility_transforms(rules: list[dict[str, Any]]) -> list[Visibility]
|
||||
```
|
||||
|
||||
|
||||
Convert rule dicts to Enabled transforms.
|
||||
Convert rule dicts to Visibility transforms.
|
||||
|
||||
|
||||
### `get_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L358" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L358" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_session_transforms(context: Context) -> list[Enabled]
|
||||
get_session_transforms(context: Context) -> list[Visibility]
|
||||
```
|
||||
|
||||
|
||||
Get session-specific Enabled transforms from state store.
|
||||
Get session-specific Visibility transforms from state store.
|
||||
|
||||
|
||||
### `enable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `enable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
enable_components(context: Context) -> None
|
||||
|
|
@ -95,7 +95,7 @@ Enable components matching criteria for this session only.
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -110,7 +110,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
|
|||
- `match_all`: If True, matches all components regardless of other criteria.
|
||||
|
||||
|
||||
### `disable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L424" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `disable_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L424" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
disable_components(context: Context) -> None
|
||||
|
|
@ -121,7 +121,7 @@ Disable components matching criteria for this session only.
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -136,10 +136,10 @@ ResourceListChangedNotification, and PromptListChangedNotification.
|
|||
- `match_all`: If True, matches all components regardless of other criteria.
|
||||
|
||||
|
||||
### `reset_components` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L478" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `reset_visibility` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L478" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
reset_components(context: Context) -> None
|
||||
reset_visibility(context: Context) -> None
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
|
|||
- `context`: The context for this session.
|
||||
|
||||
|
||||
### `apply_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L495" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `apply_session_transforms` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L495" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
apply_session_transforms(components: Sequence[ComponentT]) -> Sequence[ComponentT]
|
||||
|
|
@ -164,7 +164,7 @@ apply_session_transforms(components: Sequence[ComponentT]) -> Sequence[Component
|
|||
Apply session-specific visibility transforms to components.
|
||||
|
||||
This helper applies session-level enable/disable rules by marking
|
||||
components with their enabled state. Session transforms override
|
||||
components with their visibility state. Session transforms override
|
||||
global transforms due to mark-based semantics (later marks win).
|
||||
|
||||
**Args:**
|
||||
|
|
@ -176,28 +176,28 @@ global transforms due to mark-based semantics (later marks win).
|
|||
|
||||
## Classes
|
||||
|
||||
### `Enabled` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Visibility` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Sets enabled state on matching components.
|
||||
Sets visibility state on matching components.
|
||||
|
||||
Does NOT filter inline - just marks components with enabled state.
|
||||
Does NOT filter inline - just marks components with visibility state.
|
||||
Later transforms in the chain can override earlier marks.
|
||||
Final filtering happens at the Provider level after all transforms run.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L194" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L194" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]
|
||||
```
|
||||
|
||||
Mark tools by enabled state.
|
||||
Mark tools by visibility state.
|
||||
|
||||
|
||||
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tool(self, name: str, call_next: GetToolNext) -> Tool | None
|
||||
|
|
@ -206,16 +206,16 @@ get_tool(self, name: str, call_next: GetToolNext) -> Tool | None
|
|||
Mark tool if found.
|
||||
|
||||
|
||||
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]
|
||||
```
|
||||
|
||||
Mark resources by enabled state.
|
||||
Mark resources by visibility state.
|
||||
|
||||
|
||||
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource(self, uri: str, call_next: GetResourceNext) -> Resource | None
|
||||
|
|
@ -224,16 +224,16 @@ get_resource(self, uri: str, call_next: GetResourceNext) -> 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#L232" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L232" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resource_templates(self, templates: Sequence[ResourceTemplate]) -> Sequence[ResourceTemplate]
|
||||
```
|
||||
|
||||
Mark resource templates by enabled state.
|
||||
Mark resource templates by visibility state.
|
||||
|
||||
|
||||
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L238" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L238" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource_template(self, uri: str, call_next: GetResourceTemplateNext) -> ResourceTemplate | None
|
||||
|
|
@ -242,20 +242,19 @@ get_resource_template(self, uri: str, call_next: GetResourceTemplateNext) -> Res
|
|||
Mark resource template if found.
|
||||
|
||||
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/visibility.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]
|
||||
```
|
||||
|
||||
Mark prompts by enabled state.
|
||||
Mark prompts by visibility state.
|
||||
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/transforms/enabled.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <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
|
||||
get_prompt(self, name: str, call_next: GetPromptNext) -> Prompt | None
|
||||
```
|
||||
|
||||
Mark prompt if found.
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ The `Context` object provides a clean interface to access MCP features within yo
|
|||
- **LLM Sampling**: Request the client's LLM to generate text based on provided messages
|
||||
- **User Elicitation**: Request structured input from users during tool execution
|
||||
- **Session State**: Store data that persists across requests within an MCP session
|
||||
- **Session Visibility**: [Control which components are visible](/servers/enabled#per-session-visibility) to the current session
|
||||
- **Session Visibility**: [Control which components are visible](/servers/visibility#per-session-visibility) to the current session
|
||||
- **Request Information**: Access metadata about the current request
|
||||
- **Server Access**: When needed, access the underlying FastMCP server instance
|
||||
|
||||
|
|
@ -266,7 +266,7 @@ State set during `on_initialize` middleware persists to subsequent tool calls wh
|
|||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
Tools can customize which components are visible to their current session using `ctx.enable_components()`, `ctx.disable_components()`, and `ctx.reset_components()`. These methods apply visibility rules that affect only the calling session, leaving other sessions unchanged. See [Per-Session Visibility](/servers/enabled#per-session-visibility) for complete documentation, filter criteria, and patterns like namespace activation.
|
||||
Tools can customize which components are visible to their current session using `ctx.enable_components()`, `ctx.disable_components()`, and `ctx.reset_visibility()`. These methods apply visibility rules that affect only the calling session, leaving other sessions unchanged. See [Per-Session Visibility](/servers/visibility#per-session-visibility) for complete documentation, filter criteria, and patterns like namespace activation.
|
||||
|
||||
### Change Notifications
|
||||
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ mcp.disable(tags={"internal"})
|
|||
mcp.enable(tags={"public"}, only=True)
|
||||
```
|
||||
|
||||
See [Enabled](/servers/enabled) for the complete enabled control API including key formats, tag-based filtering, and provider-level control.
|
||||
See [Visibility](/servers/visibility) for the complete visibility control API including key formats, tag-based filtering, and provider-level control.
|
||||
|
||||
### Async Prompts
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Both providers and [middleware](/servers/middleware) can influence what componen
|
|||
|
||||
**Middleware** intercepts individual requests. It's well-suited for request-specific decisions like logging, rate limiting, or authentication.
|
||||
|
||||
You *could* use middleware to dynamically add tools based on request context. But it's often cleaner to have a provider source all possible tools, then use middleware or [enabled controls](/servers/enabled) to filter what each request can see. This separation makes it easier to reason about how components are sourced and how they interact with other server machinery.
|
||||
You *could* use middleware to dynamically add tools based on request context. But it's often cleaner to have a provider source all possible tools, then use middleware or [visibility controls](/servers/visibility) to filter what each request can see. This separation makes it easier to reason about how components are sourced and how they interact with other server machinery.
|
||||
|
||||
## The Provider Interface
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ mcp.disable(tags={"admin"})
|
|||
mcp.enable(keys={"tool:get_status"}, only=True)
|
||||
```
|
||||
|
||||
See [Enabled](/servers/enabled) for the full documentation on keys, tags, allowlist mode, and provider-level control.
|
||||
See [Visibility](/servers/visibility) for the full documentation on keys, tags, allowlist mode, and provider-level control.
|
||||
|
||||
## Standalone LocalProvider
|
||||
|
||||
|
|
@ -157,4 +157,4 @@ This is useful for:
|
|||
- Testing components in isolation
|
||||
- Building reusable component libraries
|
||||
|
||||
Standalone providers also support enabled control with `enable()` and `disable()`. See [Enabled](/servers/enabled) for details.
|
||||
Standalone providers also support visibility control with `enable()` and `disable()`. See [Visibility](/servers/visibility) for details.
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ When a client requests a tool, FastMCP queries providers in registration order.
|
|||
**Learn about providers when** you want to:
|
||||
- [Mount another server](/servers/providers/mounting) into yours
|
||||
- [Proxy a remote server](/servers/providers/proxy) through yours
|
||||
- [Control enabled state](/servers/enabled) of components
|
||||
- [Control visibility state](/servers/visibility) of components
|
||||
- [Build dynamic sources](/servers/providers/custom) like database-backed tools
|
||||
|
||||
## Next Steps
|
||||
|
|
@ -76,5 +76,5 @@ When a client requests a tool, FastMCP queries providers in registration order.
|
|||
- [Mounting](/servers/providers/mounting) - Compose servers together
|
||||
- [Proxying](/servers/providers/proxy) - Connect to remote servers
|
||||
- [Transforms](/servers/providers/transforms) - Namespace, rename, and modify components
|
||||
- [Enabled](/servers/enabled) - Control which components clients can access
|
||||
- [Visibility](/servers/visibility) - Control which components clients can access
|
||||
- [Custom](/servers/providers/custom) - Build your own providers
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ mcp.disable(tags={"internal"})
|
|||
mcp.enable(tags={"public"}, only=True)
|
||||
```
|
||||
|
||||
See [Enabled](/servers/enabled) for the complete enabled control API including key formats, tag-based filtering, and provider-level control.
|
||||
See [Visibility](/servers/visibility) for the complete visibility control API including key formats, tag-based filtering, and provider-level control.
|
||||
|
||||
|
||||
### Accessing MCP Context
|
||||
|
|
|
|||
|
|
@ -862,7 +862,7 @@ mcp.disable(tags={"admin"})
|
|||
mcp.enable(tags={"public"}, only=True)
|
||||
```
|
||||
|
||||
See [Enabled](/servers/enabled) for the complete enabled control API including key formats, tag-based filtering, and provider-level control.
|
||||
See [Visibility](/servers/visibility) for the complete visibility control API including key formats, tag-based filtering, and provider-level control.
|
||||
|
||||
## MCP Annotations
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ mcp.enable(tags={"admin"})
|
|||
|
||||
## Keys and Tags
|
||||
|
||||
Enabled filtering works with two identifiers: keys (for specific components) and tags (for groups).
|
||||
Visibility filtering works with two identifiers: keys (for specific components) and tags (for groups).
|
||||
|
||||
### Component Keys
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ 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.
|
||||
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.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -159,7 +159,7 @@ Allowlist mode is useful for restrictive environments where you want to explicit
|
|||
|
||||
When you call `enable(only=True)`:
|
||||
|
||||
1. Default enabled state switches to "disabled"
|
||||
1. Default visibility state switches to "disabled"
|
||||
2. Previous allowlists are cleared
|
||||
3. Only specified keys/tags become enabled
|
||||
|
||||
|
|
@ -186,11 +186,11 @@ You can always re-enable something that was disabled by adding another `enable()
|
|||
|
||||
## Server vs Provider
|
||||
|
||||
Enabled state operates at two levels: the server and individual providers.
|
||||
Visibility 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.
|
||||
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.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -208,13 +208,13 @@ 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.
|
||||
Each provider can add its own visibility 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
|
||||
# Create provider with visibility control
|
||||
admin_tools = LocalProvider()
|
||||
|
||||
@admin_tools.tool(tags={"admin"})
|
||||
|
|
@ -261,7 +261,7 @@ mcp.disable(tags={"beta"})
|
|||
|
||||
## Dynamic Changes
|
||||
|
||||
Enabled state changes take effect immediately. You can adjust during request handling based on context.
|
||||
Visibility state changes take effect immediately. You can adjust during request handling based on context.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -312,7 +312,7 @@ async def unlock_premium(ctx: Context) -> str:
|
|||
@mcp.tool
|
||||
async def reset_features(ctx: Context) -> str:
|
||||
"""Reset to default feature set."""
|
||||
await ctx.reset_components()
|
||||
await ctx.reset_visibility()
|
||||
return "Features reset to defaults"
|
||||
|
||||
# Premium tools are disabled globally by default
|
||||
|
|
@ -419,7 +419,7 @@ async def activate_admin(ctx: Context) -> str:
|
|||
|
||||
@server.tool
|
||||
async def deactivate_all(ctx: Context) -> str:
|
||||
await ctx.reset_components()
|
||||
await ctx.reset_visibility()
|
||||
return "All namespaces deactivated"
|
||||
|
||||
# Disable namespace tools globally
|
||||
|
|
@ -432,11 +432,11 @@ Sessions start seeing only the activation tools. Calling `activate_finance` reve
|
|||
|
||||
- **`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_components() -> None`**: Clear all session rules, returning to global defaults
|
||||
- **`await ctx.reset_visibility() -> None`**: Clear all session rules, returning to global defaults
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
|
|
@ -449,23 +449,23 @@ mcp.disable(tags={"maintenance"})
|
|||
|
||||
## Filtering Logic
|
||||
|
||||
Understanding the filtering logic helps when debugging enabled state issues.
|
||||
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.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
|
||||
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 Enabled Transform
|
||||
## The Visibility 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.
|
||||
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.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms import Enabled
|
||||
from fastmcp.server.transforms import Visibility
|
||||
|
||||
mcp = FastMCP("Server")
|
||||
|
||||
|
|
@ -473,7 +473,7 @@ mcp = FastMCP("Server")
|
|||
mcp.disable(names={"secret_tool"})
|
||||
|
||||
# Equivalent to:
|
||||
mcp.add_transform(Enabled(False, names={"secret_tool"}))
|
||||
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.
|
||||
|
|
@ -61,7 +61,7 @@ async def activate_admin(ctx: Context) -> str:
|
|||
@server.tool
|
||||
async def deactivate_all(ctx: Context) -> str:
|
||||
"""Deactivate all namespaces, returning to defaults."""
|
||||
await ctx.reset_components()
|
||||
await ctx.reset_visibility()
|
||||
return "All namespaces deactivated"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,23 +39,23 @@ from fastmcp.server.sampling.run import (
|
|||
sample_step_impl,
|
||||
)
|
||||
from fastmcp.server.server import FastMCP, StateValue
|
||||
from fastmcp.server.transforms.enabled import (
|
||||
Enabled,
|
||||
from fastmcp.server.transforms.visibility import (
|
||||
Visibility,
|
||||
)
|
||||
from fastmcp.server.transforms.enabled import (
|
||||
from fastmcp.server.transforms.visibility import (
|
||||
disable_components as _disable_components,
|
||||
)
|
||||
from fastmcp.server.transforms.enabled import (
|
||||
from fastmcp.server.transforms.visibility import (
|
||||
enable_components as _enable_components,
|
||||
)
|
||||
from fastmcp.server.transforms.enabled import (
|
||||
from fastmcp.server.transforms.visibility import (
|
||||
get_session_transforms as _get_session_transforms,
|
||||
)
|
||||
from fastmcp.server.transforms.enabled import (
|
||||
from fastmcp.server.transforms.visibility import (
|
||||
get_visibility_rules as _get_visibility_rules,
|
||||
)
|
||||
from fastmcp.server.transforms.enabled import (
|
||||
reset_components as _reset_components,
|
||||
from fastmcp.server.transforms.visibility import (
|
||||
reset_visibility as _reset_visibility,
|
||||
)
|
||||
from fastmcp.utilities.logging import _clamp_logger, get_logger
|
||||
from fastmcp.utilities.versions import VersionSpec
|
||||
|
|
@ -990,8 +990,8 @@ class Context:
|
|||
"""Load visibility rule dicts from session state."""
|
||||
return await _get_visibility_rules(self)
|
||||
|
||||
async def _get_session_transforms(self) -> list[Enabled]:
|
||||
"""Get session-specific Enabled transforms from state store."""
|
||||
async def _get_session_transforms(self) -> list[Visibility]:
|
||||
"""Get session-specific Visibility transforms from state store."""
|
||||
return await _get_session_transforms(self)
|
||||
|
||||
async def enable_components(
|
||||
|
|
@ -1009,7 +1009,7 @@ class Context:
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -1047,7 +1047,7 @@ class Context:
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -1070,7 +1070,7 @@ class Context:
|
|||
match_all=match_all,
|
||||
)
|
||||
|
||||
async def reset_components(self) -> None:
|
||||
async def reset_visibility(self) -> None:
|
||||
"""Clear all session visibility rules.
|
||||
|
||||
Use this to reset session visibility back to global defaults.
|
||||
|
|
@ -1078,7 +1078,7 @@ class Context:
|
|||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
"""
|
||||
await _reset_components(self)
|
||||
await _reset_visibility(self)
|
||||
|
||||
|
||||
async def _log_to_server_and_client(
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ from typing_extensions import Self
|
|||
from fastmcp.prompts.prompt import Prompt
|
||||
from fastmcp.resources.resource import Resource
|
||||
from fastmcp.resources.template import ResourceTemplate
|
||||
from fastmcp.server.transforms.enabled import Enabled
|
||||
from fastmcp.server.transforms.visibility import Visibility
|
||||
from fastmcp.tools.tool import Tool
|
||||
from fastmcp.utilities.async_utils import gather
|
||||
from fastmcp.utilities.components import FastMCPComponent
|
||||
|
|
@ -502,7 +502,7 @@ class Provider:
|
|||
) -> Self:
|
||||
"""Enable components matching all specified criteria.
|
||||
|
||||
Adds an enabled transform that marks matching components as enabled.
|
||||
Adds a visibility transform that marks matching components as enabled.
|
||||
Later transforms override earlier ones, so enable after disable makes
|
||||
the component enabled.
|
||||
|
||||
|
|
@ -524,9 +524,9 @@ class Provider:
|
|||
if only:
|
||||
# Allowlist: disable everything, then enable matching
|
||||
# The enable transform runs later on return path, so it overrides
|
||||
self._transforms.append(Enabled(False, match_all=True))
|
||||
self._transforms.append(Visibility(False, match_all=True))
|
||||
self._transforms.append(
|
||||
Enabled(
|
||||
Visibility(
|
||||
True,
|
||||
names=names,
|
||||
keys=keys,
|
||||
|
|
@ -550,7 +550,7 @@ class Provider:
|
|||
) -> Self:
|
||||
"""Disable components matching all specified criteria.
|
||||
|
||||
Adds an enabled transform that marks matching components as disabled.
|
||||
Adds a visibility transform that marks matching components as disabled.
|
||||
Components can be re-enabled by calling enable() with matching criteria
|
||||
(the later transform wins).
|
||||
|
||||
|
|
@ -566,7 +566,7 @@ class Provider:
|
|||
Self for method chaining.
|
||||
"""
|
||||
self._transforms.append(
|
||||
Enabled(
|
||||
Visibility(
|
||||
False,
|
||||
names=names,
|
||||
keys=keys,
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ from fastmcp.server.transforms import (
|
|||
ToolTransform,
|
||||
Transform,
|
||||
)
|
||||
from fastmcp.server.transforms.enabled import apply_session_transforms, is_enabled
|
||||
from fastmcp.server.transforms.visibility import apply_session_transforms, is_enabled
|
||||
from fastmcp.settings import DuplicateBehavior as DuplicateBehaviorSetting
|
||||
from fastmcp.settings import Settings
|
||||
from fastmcp.tools.function_tool import FunctionTool
|
||||
|
|
@ -604,7 +604,7 @@ class FastMCP(
|
|||
async def list_tools(self, *, run_middleware: bool = True) -> Sequence[Tool]:
|
||||
"""List all enabled tools from providers.
|
||||
|
||||
Overrides Provider.list_tools() to add enabled filtering, auth filtering,
|
||||
Overrides Provider.list_tools() to add visibility filtering, auth filtering,
|
||||
and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
"""
|
||||
|
|
@ -676,7 +676,7 @@ class FastMCP(
|
|||
) -> Tool | None:
|
||||
"""Get a tool by name, filtering disabled tools.
|
||||
|
||||
Overrides Provider.get_tool() to add enabled filtering after all
|
||||
Overrides Provider.get_tool() to add visibility filtering after all
|
||||
transforms (including session-level) have been applied. This ensures
|
||||
session transforms can override provider-level disables.
|
||||
|
||||
|
|
@ -702,7 +702,7 @@ class FastMCP(
|
|||
) -> Sequence[Resource]:
|
||||
"""List all enabled resources from providers.
|
||||
|
||||
Overrides Provider.list_resources() to add enabled filtering, auth filtering,
|
||||
Overrides Provider.list_resources() to add visibility filtering, auth filtering,
|
||||
and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
"""
|
||||
|
|
@ -774,7 +774,7 @@ class FastMCP(
|
|||
) -> Resource | None:
|
||||
"""Get a resource by URI, filtering disabled resources.
|
||||
|
||||
Overrides Provider.get_resource() to add enabled filtering after all
|
||||
Overrides Provider.get_resource() to add visibility filtering after all
|
||||
transforms (including session-level) have been applied.
|
||||
|
||||
Args:
|
||||
|
|
@ -799,7 +799,7 @@ class FastMCP(
|
|||
) -> Sequence[ResourceTemplate]:
|
||||
"""List all enabled resource templates from providers.
|
||||
|
||||
Overrides Provider.list_resource_templates() to add enabled filtering,
|
||||
Overrides Provider.list_resource_templates() to add visibility filtering,
|
||||
auth filtering, and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
"""
|
||||
|
|
@ -873,7 +873,7 @@ class FastMCP(
|
|||
) -> ResourceTemplate | None:
|
||||
"""Get a resource template by URI, filtering disabled templates.
|
||||
|
||||
Overrides Provider.get_resource_template() to add enabled filtering after
|
||||
Overrides Provider.get_resource_template() to add visibility filtering after
|
||||
all transforms (including session-level) have been applied.
|
||||
|
||||
Args:
|
||||
|
|
@ -896,7 +896,7 @@ class FastMCP(
|
|||
async def list_prompts(self, *, run_middleware: bool = True) -> Sequence[Prompt]:
|
||||
"""List all enabled prompts from providers.
|
||||
|
||||
Overrides Provider.list_prompts() to add enabled filtering, auth filtering,
|
||||
Overrides Provider.list_prompts() to add visibility filtering, auth filtering,
|
||||
and middleware execution. Returns all versions (no deduplication).
|
||||
Protocol handlers deduplicate for MCP wire format.
|
||||
"""
|
||||
|
|
@ -968,7 +968,7 @@ class FastMCP(
|
|||
) -> Prompt | None:
|
||||
"""Get a prompt by name, filtering disabled prompts.
|
||||
|
||||
Overrides Provider.get_prompt() to add enabled filtering after all
|
||||
Overrides Provider.get_prompt() to add visibility filtering after all
|
||||
transforms (including session-level) have been applied.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ class Transform:
|
|||
|
||||
|
||||
# Re-export built-in transforms (must be after Transform class to avoid circular imports)
|
||||
from fastmcp.server.transforms.enabled import Enabled, is_enabled # noqa: E402
|
||||
from fastmcp.server.transforms.visibility import Visibility, is_enabled # noqa: E402
|
||||
from fastmcp.server.transforms.namespace import Namespace # noqa: E402
|
||||
from fastmcp.server.transforms.prompts_as_tools import PromptsAsTools # noqa: E402
|
||||
from fastmcp.server.transforms.resources_as_tools import ResourcesAsTools # noqa: E402
|
||||
|
|
@ -228,7 +228,6 @@ from fastmcp.server.transforms.tool_transform import ToolTransform # noqa: E402
|
|||
from fastmcp.server.transforms.version_filter import VersionFilter # noqa: E402
|
||||
|
||||
__all__ = [
|
||||
"Enabled",
|
||||
"GetPromptNext",
|
||||
"GetResourceNext",
|
||||
"GetResourceTemplateNext",
|
||||
|
|
@ -240,5 +239,6 @@ __all__ = [
|
|||
"Transform",
|
||||
"VersionFilter",
|
||||
"VersionSpec",
|
||||
"Visibility",
|
||||
"is_enabled",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Enabled transform for marking component enabled state.
|
||||
"""Visibility transform for marking component visibility state.
|
||||
|
||||
Each Enabled instance marks components via internal metadata. Multiple
|
||||
enabled transforms can be stacked - later transforms override earlier ones.
|
||||
Each Visibility instance marks components via internal metadata. Multiple
|
||||
visibility transforms can be stacked - later transforms override earlier ones.
|
||||
Final filtering happens at the Provider level.
|
||||
"""
|
||||
|
||||
|
|
@ -31,29 +31,29 @@ if TYPE_CHECKING:
|
|||
|
||||
T = TypeVar("T", bound="FastMCPComponent")
|
||||
|
||||
# Enabled state stored at meta["fastmcp"]["_internal"]["enabled"]
|
||||
# Visibility state stored at meta["fastmcp"]["_internal"]["visibility"]
|
||||
_FASTMCP_KEY = "fastmcp"
|
||||
_INTERNAL_KEY = "_internal"
|
||||
|
||||
|
||||
class Enabled(Transform):
|
||||
"""Sets enabled state on matching components.
|
||||
class Visibility(Transform):
|
||||
"""Sets visibility state on matching components.
|
||||
|
||||
Does NOT filter inline - just marks components with enabled state.
|
||||
Does NOT filter inline - just marks components with visibility state.
|
||||
Later transforms in the chain can override earlier marks.
|
||||
Final filtering happens at the Provider level after all transforms run.
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Disable components tagged "internal"
|
||||
Enabled(False, tags={"internal"})
|
||||
Visibility(False, tags={"internal"})
|
||||
|
||||
# Re-enable specific tool (override earlier disable)
|
||||
Enabled(True, names={"safe_tool"})
|
||||
Visibility(True, names={"safe_tool"})
|
||||
|
||||
# Allowlist via composition:
|
||||
Enabled(False, match_all=True) # disable everything
|
||||
Enabled(True, tags={"public"}) # enable public
|
||||
Visibility(False, match_all=True) # disable everything
|
||||
Visibility(True, tags={"public"}) # enable public
|
||||
```
|
||||
"""
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ class Enabled(Transform):
|
|||
| None = None,
|
||||
match_all: bool = False,
|
||||
) -> None:
|
||||
"""Initialize an enabled marker.
|
||||
"""Initialize a visibility marker.
|
||||
|
||||
Args:
|
||||
enabled: If True, mark matching as enabled; if False, mark as disabled.
|
||||
|
|
@ -92,7 +92,7 @@ class Enabled(Transform):
|
|||
def __repr__(self) -> str:
|
||||
action = "enable" if self._enabled else "disable"
|
||||
if self.match_all:
|
||||
return f"Enabled({self._enabled}, match_all=True)"
|
||||
return f"Visibility({self._enabled}, match_all=True)"
|
||||
parts = []
|
||||
if self.names:
|
||||
parts.append(f"names={set(self.names)}")
|
||||
|
|
@ -105,8 +105,8 @@ class Enabled(Transform):
|
|||
if self.tags:
|
||||
parts.append(f"tags={set(self.tags)}")
|
||||
if parts:
|
||||
return f"Enabled({action}, {', '.join(parts)})"
|
||||
return f"Enabled({action})"
|
||||
return f"Visibility({action}, {', '.join(parts)})"
|
||||
return f"Visibility({action})"
|
||||
|
||||
def _matches(self, component: FastMCPComponent) -> bool:
|
||||
"""Check if this transform applies to the component.
|
||||
|
|
@ -171,18 +171,20 @@ class Enabled(Transform):
|
|||
return self.tags is None or bool(component.tags & self.tags)
|
||||
|
||||
def _mark_component(self, component: T) -> T:
|
||||
"""Set enabled state in component metadata if rule matches."""
|
||||
"""Set visibility state in component metadata if rule matches."""
|
||||
if not self._matches(component):
|
||||
return component
|
||||
|
||||
# Create new dicts to avoid mutating shared dicts
|
||||
# (e.g., when Tool.from_tool shares the meta dict between tools)
|
||||
if component.meta is None:
|
||||
component.meta = {_FASTMCP_KEY: {_INTERNAL_KEY: {"enabled": self._enabled}}}
|
||||
component.meta = {
|
||||
_FASTMCP_KEY: {_INTERNAL_KEY: {"visibility": self._enabled}}
|
||||
}
|
||||
else:
|
||||
old_fastmcp = component.meta.get(_FASTMCP_KEY, {})
|
||||
old_internal = old_fastmcp.get(_INTERNAL_KEY, {})
|
||||
new_internal = {**old_internal, "enabled": self._enabled}
|
||||
new_internal = {**old_internal, "visibility": self._enabled}
|
||||
new_fastmcp = {**old_fastmcp, _INTERNAL_KEY: new_internal}
|
||||
component.meta = {**component.meta, _FASTMCP_KEY: new_fastmcp}
|
||||
return component
|
||||
|
|
@ -192,7 +194,7 @@ class Enabled(Transform):
|
|||
# -------------------------------------------------------------------------
|
||||
|
||||
async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
|
||||
"""Mark tools by enabled state."""
|
||||
"""Mark tools by visibility state."""
|
||||
return [self._mark_component(t) for t in tools]
|
||||
|
||||
async def get_tool(
|
||||
|
|
@ -209,7 +211,7 @@ class Enabled(Transform):
|
|||
# -------------------------------------------------------------------------
|
||||
|
||||
async def list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]:
|
||||
"""Mark resources by enabled state."""
|
||||
"""Mark resources by visibility state."""
|
||||
return [self._mark_component(r) for r in resources]
|
||||
|
||||
async def get_resource(
|
||||
|
|
@ -232,7 +234,7 @@ class Enabled(Transform):
|
|||
async def list_resource_templates(
|
||||
self, templates: Sequence[ResourceTemplate]
|
||||
) -> Sequence[ResourceTemplate]:
|
||||
"""Mark resource templates by enabled state."""
|
||||
"""Mark resource templates by visibility state."""
|
||||
return [self._mark_component(t) for t in templates]
|
||||
|
||||
async def get_resource_template(
|
||||
|
|
@ -253,7 +255,7 @@ class Enabled(Transform):
|
|||
# -------------------------------------------------------------------------
|
||||
|
||||
async def list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]:
|
||||
"""Mark prompts by enabled state."""
|
||||
"""Mark prompts by visibility state."""
|
||||
return [self._mark_component(p) for p in prompts]
|
||||
|
||||
async def get_prompt(
|
||||
|
|
@ -270,10 +272,10 @@ def is_enabled(component: FastMCPComponent) -> bool:
|
|||
"""Check if component is enabled.
|
||||
|
||||
Returns True if:
|
||||
- No enabled mark exists (default is enabled)
|
||||
- Enabled mark is True
|
||||
- No visibility mark exists (default is enabled)
|
||||
- Visibility mark is True
|
||||
|
||||
Returns False if enabled mark is False.
|
||||
Returns False if visibility mark is False.
|
||||
|
||||
Args:
|
||||
component: Component to check.
|
||||
|
|
@ -284,7 +286,7 @@ def is_enabled(component: FastMCPComponent) -> bool:
|
|||
meta = component.meta or {}
|
||||
fastmcp = meta.get(_FASTMCP_KEY, {})
|
||||
internal = fastmcp.get(_INTERNAL_KEY, {})
|
||||
return internal.get("enabled", True) # Default True if not set
|
||||
return internal.get("visibility", True) # Default True if not set
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
@ -327,8 +329,8 @@ async def save_visibility_rules(
|
|||
await context.send_notification(mcp.types.PromptListChangedNotification())
|
||||
|
||||
|
||||
def create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]:
|
||||
"""Convert rule dicts to Enabled transforms."""
|
||||
def create_visibility_transforms(rules: list[dict[str, Any]]) -> list[Visibility]:
|
||||
"""Convert rule dicts to Visibility transforms."""
|
||||
transforms = []
|
||||
for params in rules:
|
||||
version = None
|
||||
|
|
@ -340,7 +342,7 @@ def create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]:
|
|||
eq=version_dict.get("eq"),
|
||||
)
|
||||
transforms.append(
|
||||
Enabled(
|
||||
Visibility(
|
||||
params["enabled"],
|
||||
names=set(params["names"]) if params.get("names") else None,
|
||||
keys=set(params["keys"]) if params.get("keys") else None,
|
||||
|
|
@ -355,8 +357,8 @@ def create_enabled_transforms(rules: list[dict[str, Any]]) -> list[Enabled]:
|
|||
return transforms
|
||||
|
||||
|
||||
async def get_session_transforms(context: Context) -> list[Enabled]:
|
||||
"""Get session-specific Enabled transforms from state store."""
|
||||
async def get_session_transforms(context: Context) -> list[Visibility]:
|
||||
"""Get session-specific Visibility transforms from state store."""
|
||||
try:
|
||||
# Will raise RuntimeError if no session available
|
||||
_ = context.session_id
|
||||
|
|
@ -364,7 +366,7 @@ async def get_session_transforms(context: Context) -> list[Enabled]:
|
|||
return []
|
||||
|
||||
rules = await get_visibility_rules(context)
|
||||
return create_enabled_transforms(rules)
|
||||
return create_visibility_transforms(rules)
|
||||
|
||||
|
||||
async def enable_components(
|
||||
|
|
@ -381,7 +383,7 @@ async def enable_components(
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -435,7 +437,7 @@ async def disable_components(
|
|||
|
||||
Session rules override global transforms. Rules accumulate - each call
|
||||
adds a new rule to the session. Later marks override earlier ones
|
||||
(Enabled transform semantics).
|
||||
(Visibility transform semantics).
|
||||
|
||||
Sends notifications to this session only: ToolListChangedNotification,
|
||||
ResourceListChangedNotification, and PromptListChangedNotification.
|
||||
|
|
@ -475,7 +477,7 @@ async def disable_components(
|
|||
await save_visibility_rules(context, rules, components=components)
|
||||
|
||||
|
||||
async def reset_components(context: Context) -> None:
|
||||
async def reset_visibility(context: Context) -> None:
|
||||
"""Clear all session visibility rules.
|
||||
|
||||
Use this to reset session visibility back to global defaults.
|
||||
|
|
@ -498,7 +500,7 @@ async def apply_session_transforms(
|
|||
"""Apply session-specific visibility transforms to components.
|
||||
|
||||
This helper applies session-level enable/disable rules by marking
|
||||
components with their enabled state. Session transforms override
|
||||
components with their visibility state. Session transforms override
|
||||
global transforms due to mark-based semantics (later marks win).
|
||||
|
||||
Args:
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import pytest
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.transforms.enabled import Enabled
|
||||
from fastmcp.server.transforms.visibility import Visibility
|
||||
|
||||
|
||||
class TestIncludeExcludeTagsDeprecation:
|
||||
|
|
@ -20,28 +20,28 @@ class TestIncludeExcludeTagsDeprecation:
|
|||
FastMCP(include_tags={"public"})
|
||||
|
||||
def test_exclude_tags_still_works(self):
|
||||
"""exclude_tags adds an Enabled transform that disables matching tags."""
|
||||
"""exclude_tags adds a Visibility transform that disables matching tags."""
|
||||
with pytest.warns(DeprecationWarning):
|
||||
mcp = FastMCP(exclude_tags={"internal"})
|
||||
|
||||
# Should have added an Enabled transform that disables the tag
|
||||
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Enabled)]
|
||||
# Should have added a Visibility transform that disables the tag
|
||||
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
|
||||
assert len(enabled_transforms) == 1
|
||||
e = enabled_transforms[0]
|
||||
assert e._enabled is False
|
||||
assert e.tags == {"internal"}
|
||||
|
||||
def test_include_tags_still_works(self):
|
||||
"""include_tags adds Enabled transforms for allowlist mode."""
|
||||
"""include_tags adds Visibility transforms for allowlist mode."""
|
||||
with pytest.warns(DeprecationWarning):
|
||||
mcp = FastMCP(include_tags={"public"})
|
||||
|
||||
# Should have added Enabled transforms for allowlist mode
|
||||
# Should have added Visibility transforms for allowlist mode
|
||||
# (one to disable all, one to enable matching)
|
||||
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Enabled)]
|
||||
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
|
||||
assert len(enabled_transforms) == 2
|
||||
|
||||
# First should disable all (Enabled.all(False))
|
||||
# First should disable all (Visibility.all(False))
|
||||
disable_all_transform = enabled_transforms[0]
|
||||
assert disable_all_transform._enabled is False
|
||||
assert disable_all_transform.match_all is True
|
||||
|
|
@ -59,7 +59,7 @@ class TestIncludeExcludeTagsDeprecation:
|
|||
# Should have added transforms for both
|
||||
# include_tags creates 2 (disable all + enable matching)
|
||||
# exclude_tags creates 1 (disable matching)
|
||||
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Enabled)]
|
||||
enabled_transforms = [t for t in mcp._transforms if isinstance(t, Visibility)]
|
||||
assert len(enabled_transforms) == 3
|
||||
|
||||
# Check we have both tag rules
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ class TestLocalProviderDecorators:
|
|||
assert "tool:direct_tool@" in provider._components
|
||||
|
||||
def test_tool_enabled_false(self):
|
||||
"""Tool with enabled=False should add an Enabled transform."""
|
||||
"""Tool with enabled=False should add a Visibility transform."""
|
||||
provider = LocalProvider()
|
||||
|
||||
@provider.tool(enabled=False)
|
||||
|
|
@ -344,10 +344,12 @@ class TestLocalProviderDecorators:
|
|||
return "should be disabled"
|
||||
|
||||
assert "tool:disabled_tool@" in provider._components
|
||||
# enabled=False adds an Enabled transform to disable the tool
|
||||
from fastmcp.server.transforms.enabled import Enabled
|
||||
# enabled=False adds a Visibility transform to disable the tool
|
||||
from fastmcp.server.transforms.visibility import Visibility
|
||||
|
||||
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
|
||||
enabled_transforms = [
|
||||
t for t in provider.transforms if isinstance(t, Visibility)
|
||||
]
|
||||
assert len(enabled_transforms) == 1
|
||||
assert enabled_transforms[0]._enabled is False
|
||||
assert enabled_transforms[0].keys == {"tool:disabled_tool@"}
|
||||
|
|
@ -425,7 +427,7 @@ class TestLocalProviderDecorators:
|
|||
assert provider._components["resource:resource://test@"].name == "custom_name"
|
||||
|
||||
def test_resource_enabled_false(self):
|
||||
"""Resource with enabled=False should add an Enabled transform."""
|
||||
"""Resource with enabled=False should add a Visibility transform."""
|
||||
provider = LocalProvider()
|
||||
|
||||
@provider.resource("resource://test", enabled=False)
|
||||
|
|
@ -433,10 +435,12 @@ class TestLocalProviderDecorators:
|
|||
return "should be disabled"
|
||||
|
||||
assert "resource:resource://test@" in provider._components
|
||||
# enabled=False adds an Enabled transform to disable the resource
|
||||
from fastmcp.server.transforms.enabled import Enabled
|
||||
# enabled=False adds a Visibility transform to disable the resource
|
||||
from fastmcp.server.transforms.visibility import Visibility
|
||||
|
||||
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
|
||||
enabled_transforms = [
|
||||
t for t in provider.transforms if isinstance(t, Visibility)
|
||||
]
|
||||
assert len(enabled_transforms) == 1
|
||||
assert enabled_transforms[0]._enabled is False
|
||||
assert enabled_transforms[0].keys == {"resource:resource://test@"}
|
||||
|
|
@ -461,7 +465,7 @@ class TestLocalProviderDecorators:
|
|||
assert "resource://disabled" not in uris
|
||||
|
||||
def test_template_enabled_false(self):
|
||||
"""Template with enabled=False should add an Enabled transform."""
|
||||
"""Template with enabled=False should add a Visibility transform."""
|
||||
provider = LocalProvider()
|
||||
|
||||
@provider.resource("data://{id}", enabled=False)
|
||||
|
|
@ -469,10 +473,12 @@ class TestLocalProviderDecorators:
|
|||
return f"Data {id}"
|
||||
|
||||
assert "template:data://{id}@" in provider._components
|
||||
# enabled=False adds an Enabled transform to disable the template
|
||||
from fastmcp.server.transforms.enabled import Enabled
|
||||
# enabled=False adds a Visibility transform to disable the template
|
||||
from fastmcp.server.transforms.visibility import Visibility
|
||||
|
||||
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
|
||||
enabled_transforms = [
|
||||
t for t in provider.transforms if isinstance(t, Visibility)
|
||||
]
|
||||
assert len(enabled_transforms) == 1
|
||||
assert enabled_transforms[0]._enabled is False
|
||||
assert enabled_transforms[0].keys == {"template:data://{id}@"}
|
||||
|
|
@ -532,7 +538,7 @@ class TestLocalProviderDecorators:
|
|||
assert "prompt:my_prompt@" not in provider._components
|
||||
|
||||
def test_prompt_enabled_false(self):
|
||||
"""Prompt with enabled=False should add an Enabled transform."""
|
||||
"""Prompt with enabled=False should add a Visibility transform."""
|
||||
provider = LocalProvider()
|
||||
|
||||
@provider.prompt(enabled=False)
|
||||
|
|
@ -540,10 +546,12 @@ class TestLocalProviderDecorators:
|
|||
return "should be disabled"
|
||||
|
||||
assert "prompt:disabled_prompt@" in provider._components
|
||||
# enabled=False adds an Enabled transform to disable the prompt
|
||||
from fastmcp.server.transforms.enabled import Enabled
|
||||
# enabled=False adds a Visibility transform to disable the prompt
|
||||
from fastmcp.server.transforms.visibility import Visibility
|
||||
|
||||
enabled_transforms = [t for t in provider.transforms if isinstance(t, Enabled)]
|
||||
enabled_transforms = [
|
||||
t for t in provider.transforms if isinstance(t, Visibility)
|
||||
]
|
||||
assert len(enabled_transforms) == 1
|
||||
assert enabled_transforms[0]._enabled is False
|
||||
assert enabled_transforms[0].keys == {"prompt:disabled_prompt@"}
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ class TestSessionVisibility:
|
|||
assert not any(t.name == "old_tool" for t in tools)
|
||||
|
||||
async def test_clear_visibility_rules(self):
|
||||
"""Test that reset_components removes all session rules."""
|
||||
"""Test that reset_visibility removes all session rules."""
|
||||
from fastmcp import Client
|
||||
|
||||
mcp = FastMCP("test")
|
||||
|
|
@ -245,7 +245,7 @@ class TestSessionVisibility:
|
|||
|
||||
@mcp.tool
|
||||
async def clear_rules(ctx: Context) -> str:
|
||||
await ctx.reset_components()
|
||||
await ctx.reset_visibility()
|
||||
rules = await ctx._get_visibility_rules()
|
||||
assert len(rules) == 0
|
||||
return "cleared"
|
||||
|
|
@ -449,14 +449,14 @@ class TestSessionVisibilityNotifications:
|
|||
)
|
||||
|
||||
async def test_clear_visibility_rules_sends_notifications(self):
|
||||
"""Test that reset_components sends notifications."""
|
||||
"""Test that reset_visibility sends notifications."""
|
||||
from fastmcp import Client
|
||||
|
||||
mcp = FastMCP("test")
|
||||
|
||||
@mcp.tool
|
||||
async def clear(ctx: Context) -> str:
|
||||
await ctx.reset_components()
|
||||
await ctx.reset_visibility()
|
||||
return "cleared"
|
||||
|
||||
handler = RecordingMessageHandler()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"""Tests for Enabled transform."""
|
||||
"""Tests for Visibility transform."""
|
||||
|
||||
import pytest
|
||||
|
||||
from fastmcp.server.transforms.enabled import Enabled, is_enabled
|
||||
from fastmcp.server.transforms.visibility import Visibility, is_enabled
|
||||
from fastmcp.tools.tool import Tool
|
||||
from fastmcp.utilities.versions import VersionSpec
|
||||
|
||||
|
|
@ -12,43 +12,43 @@ class TestMatching:
|
|||
|
||||
def test_empty_criteria_matches_nothing(self):
|
||||
"""Empty criteria is a safe default - matches nothing."""
|
||||
t = Enabled(False)
|
||||
t = Visibility(False)
|
||||
assert t._matches(Tool(name="anything", parameters={})) is False
|
||||
|
||||
def test_match_all_matches_everything(self):
|
||||
"""match_all=True matches all components."""
|
||||
t = Enabled(False, match_all=True)
|
||||
t = Visibility(False, match_all=True)
|
||||
assert t._matches(Tool(name="anything", parameters={})) is True
|
||||
|
||||
def test_match_by_name(self):
|
||||
"""Matches component by name."""
|
||||
t = Enabled(False, names={"foo"})
|
||||
t = Visibility(False, names={"foo"})
|
||||
assert t._matches(Tool(name="foo", parameters={})) is True
|
||||
assert t._matches(Tool(name="bar", parameters={})) is False
|
||||
|
||||
def test_match_by_version(self):
|
||||
"""Matches component by version."""
|
||||
t = Enabled(False, version=VersionSpec(eq="v1"))
|
||||
t = Visibility(False, version=VersionSpec(eq="v1"))
|
||||
assert t._matches(Tool(name="foo", version="v1", parameters={})) is True
|
||||
assert t._matches(Tool(name="foo", version="v2", parameters={})) is False
|
||||
|
||||
def test_match_by_version_spec_exact(self):
|
||||
"""VersionSpec(eq="v1") matches v1 only."""
|
||||
t = Enabled(False, version=VersionSpec(eq="v1"))
|
||||
t = Visibility(False, version=VersionSpec(eq="v1"))
|
||||
assert t._matches(Tool(name="foo", version="v1", parameters={})) is True
|
||||
assert t._matches(Tool(name="foo", version="v2", parameters={})) is False
|
||||
assert t._matches(Tool(name="foo", version="v0", parameters={})) is False
|
||||
|
||||
def test_match_by_version_spec_gte(self):
|
||||
"""VersionSpec(gte="v2") matches v2, v3, but not v1."""
|
||||
t = Enabled(False, version=VersionSpec(gte="v2"))
|
||||
t = Visibility(False, version=VersionSpec(gte="v2"))
|
||||
assert t._matches(Tool(name="foo", version="v1", parameters={})) is False
|
||||
assert t._matches(Tool(name="foo", version="v2", parameters={})) is True
|
||||
assert t._matches(Tool(name="foo", version="v3", parameters={})) is True
|
||||
|
||||
def test_match_by_version_spec_range(self):
|
||||
"""VersionSpec(gte="v1", lt="v3") matches v1, v2, but not v3."""
|
||||
t = Enabled(False, version=VersionSpec(gte="v1", lt="v3"))
|
||||
t = Visibility(False, version=VersionSpec(gte="v1", lt="v3"))
|
||||
assert t._matches(Tool(name="foo", version="v0", parameters={})) is False
|
||||
assert t._matches(Tool(name="foo", version="v1", parameters={})) is True
|
||||
assert t._matches(Tool(name="foo", version="v2", parameters={})) is True
|
||||
|
|
@ -57,27 +57,27 @@ class TestMatching:
|
|||
|
||||
def test_unversioned_does_not_match_version_spec(self):
|
||||
"""Unversioned components (version=None) don't match a VersionSpec."""
|
||||
t = Enabled(False, version=VersionSpec(eq="v1"))
|
||||
t = Visibility(False, version=VersionSpec(eq="v1"))
|
||||
assert t._matches(Tool(name="foo", parameters={})) is False
|
||||
|
||||
t2 = Enabled(False, version=VersionSpec(gte="v1"))
|
||||
t2 = Visibility(False, version=VersionSpec(gte="v1"))
|
||||
assert t2._matches(Tool(name="foo", parameters={})) is False
|
||||
|
||||
def test_match_by_tag(self):
|
||||
"""Matches if component has any of the specified tags."""
|
||||
t = Enabled(False, tags=set({"internal", "deprecated"}))
|
||||
t = Visibility(False, tags=set({"internal", "deprecated"}))
|
||||
assert t._matches(Tool(name="foo", parameters={}, tags={"internal"})) is True
|
||||
assert t._matches(Tool(name="foo", parameters={}, tags={"public"})) is False
|
||||
|
||||
def test_match_by_component_type(self):
|
||||
"""Only matches specified component types."""
|
||||
t = Enabled(False, names={"foo"}, components={"prompt"})
|
||||
t = Visibility(False, names={"foo"}, components={"prompt"})
|
||||
# Tool has key "tool:foo@", not "prompt:foo@"
|
||||
assert t._matches(Tool(name="foo", parameters={})) is False
|
||||
|
||||
def test_all_criteria_must_match(self):
|
||||
"""Multiple criteria use AND logic - all must match."""
|
||||
t = Enabled(
|
||||
t = Visibility(
|
||||
False,
|
||||
names={"foo"},
|
||||
version=VersionSpec(eq="v1"),
|
||||
|
|
@ -96,26 +96,26 @@ class TestMatching:
|
|||
|
||||
|
||||
class TestMarking:
|
||||
"""Test enabled state marking."""
|
||||
"""Test visibility state marking."""
|
||||
|
||||
def test_disable_marks_as_disabled(self):
|
||||
"""Enabled(False, ...) marks matching components as disabled."""
|
||||
"""Visibility(False, ...) marks matching components as disabled."""
|
||||
tool = Tool(name="foo", parameters={})
|
||||
Enabled(False, names={"foo"})._mark_component(tool)
|
||||
Visibility(False, names={"foo"})._mark_component(tool)
|
||||
assert is_enabled(tool) is False
|
||||
|
||||
def test_enable_marks_as_enabled(self):
|
||||
"""Enabled(True, ...) marks matching components as enabled."""
|
||||
"""Visibility(True, ...) marks matching components as enabled."""
|
||||
tool = Tool(name="foo", parameters={})
|
||||
Enabled(True, names={"foo"})._mark_component(tool)
|
||||
Visibility(True, names={"foo"})._mark_component(tool)
|
||||
assert is_enabled(tool) is True
|
||||
assert tool.meta is not None
|
||||
assert tool.meta["fastmcp"]["_internal"]["enabled"] is True
|
||||
assert tool.meta["fastmcp"]["_internal"]["visibility"] is True
|
||||
|
||||
def test_non_matching_unchanged(self):
|
||||
"""Non-matching components are not modified."""
|
||||
tool = Tool(name="bar", parameters={})
|
||||
Enabled(False, names={"foo"})._mark_component(tool)
|
||||
Visibility(False, names={"foo"})._mark_component(tool)
|
||||
# No _internal key added
|
||||
assert tool.meta is None or "_internal" not in tool.meta.get("fastmcp", {})
|
||||
assert is_enabled(tool) is True
|
||||
|
|
@ -123,13 +123,13 @@ class TestMarking:
|
|||
def test_mutates_in_place(self):
|
||||
"""Marking mutates the component in place."""
|
||||
tool = Tool(name="foo", parameters={})
|
||||
result = Enabled(False, names={"foo"})._mark_component(tool)
|
||||
result = Visibility(False, names={"foo"})._mark_component(tool)
|
||||
assert result is tool
|
||||
|
||||
def test_disable_all(self):
|
||||
"""match_all=True disables all components."""
|
||||
tool = Tool(name="anything", parameters={})
|
||||
Enabled(False, match_all=True)._mark_component(tool)
|
||||
Visibility(False, match_all=True)._mark_component(tool)
|
||||
assert is_enabled(tool) is False
|
||||
|
||||
|
||||
|
|
@ -139,19 +139,19 @@ class TestOverride:
|
|||
def test_enable_overrides_disable(self):
|
||||
"""An enable after disable results in enabled."""
|
||||
tool = Tool(name="foo", parameters={})
|
||||
Enabled(False, names={"foo"})._mark_component(tool)
|
||||
Visibility(False, names={"foo"})._mark_component(tool)
|
||||
assert is_enabled(tool) is False
|
||||
|
||||
Enabled(True, names={"foo"})._mark_component(tool)
|
||||
Visibility(True, names={"foo"})._mark_component(tool)
|
||||
assert is_enabled(tool) is True
|
||||
|
||||
def test_disable_overrides_enable(self):
|
||||
"""A disable after enable results in disabled."""
|
||||
tool = Tool(name="foo", parameters={})
|
||||
Enabled(True, names={"foo"})._mark_component(tool)
|
||||
Visibility(True, names={"foo"})._mark_component(tool)
|
||||
assert is_enabled(tool) is True
|
||||
|
||||
Enabled(False, names={"foo"})._mark_component(tool)
|
||||
Visibility(False, names={"foo"})._mark_component(tool)
|
||||
assert is_enabled(tool) is False
|
||||
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ class TestHelperFunctions:
|
|||
Tool(name="enabled", parameters={}),
|
||||
Tool(name="disabled", parameters={}),
|
||||
]
|
||||
Enabled(False, names={"disabled"})._mark_component(tools[1])
|
||||
Visibility(False, names={"disabled"})._mark_component(tools[1])
|
||||
|
||||
visible = [t for t in tools if is_enabled(t)]
|
||||
assert [t.name for t in visible] == ["enabled"]
|
||||
|
|
@ -181,7 +181,7 @@ class TestMetadata:
|
|||
def test_internal_metadata_stripped_by_get_meta(self):
|
||||
"""Internal metadata is stripped when calling get_meta()."""
|
||||
tool = Tool(name="foo", parameters={})
|
||||
Enabled(True, names={"foo"})._mark_component(tool)
|
||||
Visibility(True, names={"foo"})._mark_component(tool)
|
||||
|
||||
# Raw meta has _internal
|
||||
assert tool.meta is not None
|
||||
|
|
@ -194,7 +194,7 @@ class TestMetadata:
|
|||
def test_user_metadata_preserved(self):
|
||||
"""User-provided metadata is not affected."""
|
||||
tool = Tool(name="foo", parameters={}, meta={"custom": "value"})
|
||||
marked = Enabled(False, names={"foo"})._mark_component(tool)
|
||||
marked = Visibility(False, names={"foo"})._mark_component(tool)
|
||||
|
||||
assert marked.meta is not None
|
||||
assert marked.meta["custom"] == "value"
|
||||
|
|
@ -205,24 +205,24 @@ class TestRepr:
|
|||
|
||||
def test_repr_disable(self):
|
||||
"""Repr shows disable action and criteria."""
|
||||
t = Enabled(False, names={"foo"})
|
||||
t = Visibility(False, names={"foo"})
|
||||
r = repr(t)
|
||||
assert "disable" in r
|
||||
assert "foo" in r
|
||||
|
||||
def test_repr_enable(self):
|
||||
"""Repr shows enable action."""
|
||||
t = Enabled(True, names={"foo"})
|
||||
t = Visibility(True, names={"foo"})
|
||||
assert "enable" in repr(t)
|
||||
|
||||
def test_repr_match_all(self):
|
||||
"""Repr shows match_all."""
|
||||
t = Enabled(False, match_all=True)
|
||||
t = Visibility(False, match_all=True)
|
||||
assert "match_all=True" in repr(t)
|
||||
|
||||
|
||||
class TestTransformChain:
|
||||
"""Test Enabled in async transform chains."""
|
||||
"""Test Visibility in async transform chains."""
|
||||
|
||||
@pytest.fixture
|
||||
def tools(self):
|
||||
|
|
@ -234,7 +234,7 @@ class TestTransformChain:
|
|||
|
||||
async def test_list_tools_marks_matching(self, tools):
|
||||
"""list_tools applies marks to matching components."""
|
||||
disable_internal = Enabled(False, tags=set({"internal"}))
|
||||
disable_internal = Visibility(False, tags=set({"internal"}))
|
||||
|
||||
result = await disable_internal.list_tools(tools)
|
||||
|
||||
|
|
@ -245,8 +245,8 @@ class TestTransformChain:
|
|||
|
||||
async def test_later_transform_overrides(self, tools):
|
||||
"""Later transforms in chain override earlier ones."""
|
||||
disable_internal = Enabled(False, tags=set({"internal"}))
|
||||
enable_safe = Enabled(True, tags=set({"safe"}))
|
||||
disable_internal = Visibility(False, tags=set({"internal"}))
|
||||
enable_safe = Visibility(True, tags=set({"safe"}))
|
||||
|
||||
# Apply transforms sequentially
|
||||
after_disable = await disable_internal.list_tools(tools)
|
||||
|
|
@ -260,8 +260,8 @@ class TestTransformChain:
|
|||
|
||||
async def test_allowlist_pattern(self, tools):
|
||||
"""Disable all, then enable specific = allowlist."""
|
||||
disable_all = Enabled(False, match_all=True)
|
||||
enable_public = Enabled(True, tags=set({"public"}))
|
||||
disable_all = Visibility(False, match_all=True)
|
||||
enable_public = Visibility(True, tags=set({"public"}))
|
||||
|
||||
# Apply transforms sequentially
|
||||
after_disable = await disable_all.list_tools(tools)
|
||||
Loading…
Add table
Add a link
Reference in a new issue