Remove resource_prefix_format="protocol" support (deprecated 2.4.0) (#2342)

* Remove resource_prefix_format="protocol" support (fixes #2195)

Removes deprecated protocol format (prefix+resource://path) and keeps only
path format (resource://prefix/path). Since only one format remains:

- Removed resource_prefix_format from settings, FastMCP.__init__, and helpers
- Simplified add_resource_prefix, remove_resource_prefix, has_resource_prefix
- Removed MountedServer.resource_prefix_format field
- Deleted tests for protocol format

All resource prefixes now use path format exclusively.

* Clean up resource_prefix_format references

- Remove from test files
- Update documentation to remove protocol format section
- Move custom HTTP routes note to mounting section
- Remove resource_prefix_format from settings docs

* Use inline version note instead of badge for prefix format

* Remove obsolete test functions and update docs

- Delete test functions that no longer assert anything
- Remove proxy.mdx reference to deleted prefix format section

* Format error messages per ruff
This commit is contained in:
Jeremiah Lowin 2025-11-01 13:24:56 -07:00 committed by GitHub
commit e257b76bc6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 52 additions and 451 deletions

View file

@ -1234,7 +1234,7 @@ This release introduces completely new tools for generating and customizing MCP
## [v2.4.0: Config and Conquer](https://github.com/jlowin/fastmcp/releases/tag/v2.4.0)
**Note**: this release includes a backwards-incompatible change to how resources are prefixed when mounted in composed servers. However, it is only backwards-incompatible if users were running tests or manually loading resources by prefixed key; LLMs should not have any issue discovering the new route. See [Resource Prefix Formats](https://gofastmcp.com/servers/composition#resource-prefix-formats) for more.
**Note**: this release includes a backwards-incompatible change to how resources are prefixed when mounted in composed servers. However, it is only backwards-incompatible if users were running tests or manually loading resources by prefixed key; LLMs should not have any issue discovering the new route.
### New Features 🎉

View file

@ -41,7 +41,7 @@ FastMCP supports [MCP proxying](/servers/proxy), which allows you to mirror a lo
You can also create proxies from configuration dictionaries that follow the MCPConfig schema, which is useful for quickly connecting to one or more remote servers. See the [Proxy Servers documentation](/servers/proxy#configuration-based-proxies) for details on configuration-based proxying. Note that MCPConfig follows an emerging standard and its format may evolve over time.
Prefixing rules for tools, prompts, resources, and templates are identical across importing, mounting, and proxies.
Prefixing rules for tools, prompts, resources, and templates are identical across importing, mounting, and proxies. When prefixes are used, resource URIs are prefixed using path format (since 2.4.0): `resource://prefix/path/to/resource`.
## Importing (Static Composition)
@ -207,6 +207,10 @@ The same prefixing rules apply as with `import_server` for naming tools, resourc
The `prefix` parameter is optional. If omitted, components are mounted without modification.
</Tip>
<Note>
When mounting servers, custom HTTP routes defined with `@server.custom_route()` are also forwarded to the parent server, making them accessible through the parent's HTTP application.
</Note>
#### Performance Considerations
Due to the "live link", operations like `list_tools()` on the parent server will be impacted by the speed of the slowest mounted server. In particular, HTTP-based mounted servers can introduce significant latency (300-400ms vs 1-2ms for local tools), and this slowdown affects the whole server, not just interactions with the HTTP-proxied tools. If performance is important, importing tools via [`import_server()`](#importing-static-composition) may be a more appropriate solution as it copies components once at startup rather than delegating requests at runtime.
@ -320,62 +324,3 @@ This ensures that parent server tag policies act as a global policy for everythi
<Note>
This filtering applies to both **listing** (e.g., `list_tools()`) and **execution** (e.g., `call_tool()`). Filtered components are neither visible nor executable through the parent server.
</Note>
## Resource Prefix Formats
<VersionBadge version="2.4.0" />
When mounting or importing servers, resource URIs are usually prefixed to avoid naming conflicts. FastMCP supports two different formats for resource prefixes:
### Path Format (Default)
In path format, prefixes are added to the path component of the URI:
```
resource://prefix/path/to/resource
```
This is the default format since FastMCP 2.4. This format is recommended because it avoids issues with URI protocol restrictions (like underscores not being allowed in protocol names).
### Protocol Format (Legacy)
In protocol format, prefixes are added as part of the protocol:
```
prefix+resource://path/to/resource
```
This was the default format in FastMCP before 2.4. While still supported, it's not recommended for new code as it can cause problems with prefix names that aren't valid in URI protocols.
### Configuring the Prefix Format
You can configure the prefix format globally in code:
```python
import fastmcp
fastmcp.settings.resource_prefix_format = "protocol"
```
Or via environment variable:
```bash
FASTMCP_RESOURCE_PREFIX_FORMAT=protocol
```
Or per-server:
```python
from fastmcp import FastMCP
# Create a server that uses legacy protocol format
server = FastMCP("LegacyServer", resource_prefix_format="protocol")
# Create a server that uses new path format
server = FastMCP("NewServer", resource_prefix_format="path")
```
When mounting or importing servers, the prefix format of the parent server is used.
<Note>
When mounting servers, custom HTTP routes defined with `@server.custom_route()` are also forwarded to the parent server, making them accessible through the parent's HTTP application.
</Note>

View file

@ -266,8 +266,6 @@ These rules apply uniformly whether you:
- Create a multi-server proxy from an `MCPConfig`
- Use `FastMCP.as_proxy()` directly
For resource URI prefix formats (path vs legacy protocol style) and configuration options, see Server Composition → Resource Prefix Formats.
## Mirrored Components
<VersionBadge version="2.10.5" />

View file

@ -357,7 +357,6 @@ import fastmcp
# Access global settings
print(fastmcp.settings.log_level) # Default: "INFO"
print(fastmcp.settings.mask_error_details) # Default: False
print(fastmcp.settings.resource_prefix_format) # Default: "path"
print(fastmcp.settings.strict_input_validation) # Default: False
print(fastmcp.settings.include_fastmcp_meta) # Default: True
```
@ -365,7 +364,6 @@ print(fastmcp.settings.include_fastmcp_meta) # Default: True
Common global settings include:
- **`log_level`**: Logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), set with `FASTMCP_LOG_LEVEL`
- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS`
- **`resource_prefix_format`**: How to format resource prefixes ("path" or "protocol"), set with `FASTMCP_RESOURCE_PREFIX_FORMAT`
- **`strict_input_validation`**: Controls tool input validation mode (default: False for flexible coercion), set with `FASTMCP_STRICT_INPUT_VALIDATION`. See [Input Validation Modes](/servers/tools#input-validation-modes)
- **`include_fastmcp_meta`**: Whether to include FastMCP metadata in component responses (default: True), set with `FASTMCP_INCLUDE_FASTMCP_META`
- **`env_file`**: Path to the environment file to load settings from (default: ".env"), set with `FASTMCP_ENV_FILE`. Useful when your project uses a `.env` file with syntax incompatible with python-dotenv
@ -399,7 +397,6 @@ Global FastMCP settings can be configured via environment variables (prefixed wi
# Configure global FastMCP behavior
export FASTMCP_LOG_LEVEL=DEBUG
export FASTMCP_MASK_ERROR_DETAILS=True
export FASTMCP_RESOURCE_PREFIX_FORMAT=protocol
export FASTMCP_STRICT_INPUT_VALIDATION=False
export FASTMCP_INCLUDE_FASTMCP_META=False
```

View file

@ -105,16 +105,8 @@ class ComponentService:
# 2. Check mounted servers using the filtered protocol path.
for mounted in reversed(self._server._mounted_servers):
if mounted.prefix:
if has_resource_prefix(
key,
mounted.prefix,
mounted.resource_prefix_format,
):
key = remove_resource_prefix(
key,
mounted.prefix,
mounted.resource_prefix_format,
)
if has_resource_prefix(key, mounted.prefix):
key = remove_resource_prefix(key, mounted.prefix)
mounted_service = ComponentService(mounted.server)
mounted_resource: (
Resource | ResourceTemplate
@ -148,16 +140,8 @@ class ComponentService:
# 2. Check mounted servers using the filtered protocol path.
for mounted in reversed(self._server._mounted_servers):
if mounted.prefix:
if has_resource_prefix(
key,
mounted.prefix,
mounted.resource_prefix_format,
):
key = remove_resource_prefix(
key,
mounted.prefix,
mounted.resource_prefix_format,
)
if has_resource_prefix(key, mounted.prefix):
key = remove_resource_prefix(key, mounted.prefix)
mounted_service = ComponentService(mounted.server)
mounted_resource: (
Resource | ResourceTemplate

View file

@ -156,7 +156,6 @@ class FastMCP(Generic[LifespanResultT]):
auth: AuthProvider | NotSetT | None = NotSet,
middleware: Sequence[Middleware] | None = None,
lifespan: LifespanCallable | None = None,
resource_prefix_format: Literal["protocol", "path"] | None = None,
mask_error_details: bool | None = None,
tools: Sequence[Tool | Callable[..., Any]] | None = None,
tool_transformations: Mapping[str, ToolTransformConfig] | None = None,
@ -185,10 +184,6 @@ class FastMCP(Generic[LifespanResultT]):
sampling_handler: ServerSamplingHandler[LifespanResultT] | None = None,
sampling_handler_behavior: Literal["always", "fallback"] | None = None,
):
self.resource_prefix_format: Literal["protocol", "path"] = (
resource_prefix_format or fastmcp.settings.resource_prefix_format
)
self._additional_http_routes: list[BaseRoute] = []
self._mounted_servers: list[MountedServer] = []
self._tool_manager: ToolManager = ToolManager(
@ -495,9 +490,7 @@ class FastMCP(Generic[LifespanResultT]):
child_resources = await mounted.server.get_resources()
for key, resource in child_resources.items():
new_key = (
add_resource_prefix(
key, mounted.prefix, mounted.resource_prefix_format
)
add_resource_prefix(key, mounted.prefix)
if mounted.prefix
else key
)
@ -534,9 +527,7 @@ class FastMCP(Generic[LifespanResultT]):
child_templates = await mounted.server.get_resource_templates()
for key, template in child_templates.items():
new_key = (
add_resource_prefix(
key, mounted.prefix, mounted.resource_prefix_format
)
add_resource_prefix(key, mounted.prefix)
if mounted.prefix
else key
)
@ -814,11 +805,7 @@ class FastMCP(Generic[LifespanResultT]):
key = resource.key
if mounted.prefix:
key = add_resource_prefix(
resource.key,
mounted.prefix,
mounted.resource_prefix_format,
)
key = add_resource_prefix(resource.key, mounted.prefix)
resource = resource.model_copy(
key=key,
update={"name": f"{mounted.prefix}_{resource.name}"},
@ -910,11 +897,7 @@ class FastMCP(Generic[LifespanResultT]):
key = template.key
if mounted.prefix:
key = add_resource_prefix(
template.key,
mounted.prefix,
mounted.resource_prefix_format,
)
key = add_resource_prefix(template.key, mounted.prefix)
template = template.model_copy(
key=key,
update={"name": f"{mounted.prefix}_{template.name}"},
@ -1170,13 +1153,9 @@ class FastMCP(Generic[LifespanResultT]):
for mounted in reversed(self._mounted_servers):
key = uri_str
if mounted.prefix:
if not has_resource_prefix(
key, mounted.prefix, mounted.resource_prefix_format
):
if not has_resource_prefix(key, mounted.prefix):
continue
key = remove_resource_prefix(
key, mounted.prefix, mounted.resource_prefix_format
)
key = remove_resource_prefix(key, mounted.prefix)
try:
# First, get the resource to check if parent's filter allows it
@ -2197,7 +2176,6 @@ class FastMCP(Generic[LifespanResultT]):
mounted_server = MountedServer(
prefix=prefix,
server=server,
resource_prefix_format=self.resource_prefix_format,
)
self._mounted_servers.append(mounted_server)
@ -2298,9 +2276,7 @@ class FastMCP(Generic[LifespanResultT]):
# Import resources and templates from the server
for key, resource in (await server.get_resources()).items():
if prefix:
resource_key = add_resource_prefix(
key, prefix, self.resource_prefix_format
)
resource_key = add_resource_prefix(key, prefix)
resource = resource.model_copy(
update={"name": f"{prefix}_{resource.name}"}, key=resource_key
)
@ -2308,9 +2284,7 @@ class FastMCP(Generic[LifespanResultT]):
for key, template in (await server.get_resource_templates()).items():
if prefix:
template_key = add_resource_prefix(
key, prefix, self.resource_prefix_format
)
template_key = add_resource_prefix(key, prefix)
template = template.model_copy(
update={"name": f"{prefix}_{template.name}"}, key=template_key
)
@ -2572,13 +2546,10 @@ class FastMCP(Generic[LifespanResultT]):
class MountedServer:
prefix: str | None
server: FastMCP[Any]
resource_prefix_format: Literal["protocol", "path"] | None = None
def add_resource_prefix(
uri: str, prefix: str, prefix_format: Literal["protocol", "path"] | None = None
) -> str:
"""Add a prefix to a resource URI.
def add_resource_prefix(uri: str, prefix: str) -> str:
"""Add a prefix to a resource URI using path formatting (resource://prefix/path).
Args:
uri: The original resource URI
@ -2588,16 +2559,10 @@ def add_resource_prefix(
The resource URI with the prefix added
Examples:
With new style:
```python
add_resource_prefix("resource://path/to/resource", "prefix")
"resource://prefix/path/to/resource"
```
With legacy style:
```python
add_resource_prefix("resource://path/to/resource", "prefix")
"prefix+resource://path/to/resource"
```
With absolute path:
```python
add_resource_prefix("resource:///absolute/path", "prefix")
@ -2610,54 +2575,32 @@ def add_resource_prefix(
if not prefix:
return uri
# Get the server settings to check for legacy format preference
# Split the URI into protocol and path
match = URI_PATTERN.match(uri)
if not match:
raise ValueError(f"Invalid URI format: {uri}. Expected protocol://path format.")
if prefix_format is None:
prefix_format = fastmcp.settings.resource_prefix_format
protocol, path = match.groups()
if prefix_format == "protocol":
# Legacy style: prefix+protocol://path
return f"{prefix}+{uri}"
elif prefix_format == "path":
# New style: protocol://prefix/path
# Split the URI into protocol and path
match = URI_PATTERN.match(uri)
if not match:
raise ValueError(
f"Invalid URI format: {uri}. Expected protocol://path format."
)
protocol, path = match.groups()
# Add the prefix to the path
return f"{protocol}{prefix}/{path}"
else:
raise ValueError(f"Invalid prefix format: {prefix_format}")
# Add the prefix to the path
return f"{protocol}{prefix}/{path}"
def remove_resource_prefix(
uri: str, prefix: str, prefix_format: Literal["protocol", "path"] | None = None
) -> str:
def remove_resource_prefix(uri: str, prefix: str) -> str:
"""Remove a prefix from a resource URI.
Args:
uri: The resource URI with a prefix
prefix: The prefix to remove
prefix_format: The format of the prefix to remove
Returns:
The resource URI with the prefix removed
Examples:
With new style:
```python
remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
"resource://path/to/resource"
```
With legacy style:
```python
remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
"resource://path/to/resource"
```
With absolute path:
```python
remove_resource_prefix("resource://prefix//absolute/path", "prefix")
@ -2670,41 +2613,24 @@ def remove_resource_prefix(
if not prefix:
return uri
if prefix_format is None:
prefix_format = fastmcp.settings.resource_prefix_format
# Split the URI into protocol and path
match = URI_PATTERN.match(uri)
if not match:
raise ValueError(f"Invalid URI format: {uri}. Expected protocol://path format.")
if prefix_format == "protocol":
# Legacy style: prefix+protocol://path
legacy_prefix = f"{prefix}+"
if uri.startswith(legacy_prefix):
return uri[len(legacy_prefix) :]
protocol, path = match.groups()
# Check if the path starts with the prefix followed by a /
prefix_pattern = f"^{re.escape(prefix)}/(.*?)$"
path_match = re.match(prefix_pattern, path)
if not path_match:
return uri
elif prefix_format == "path":
# New style: protocol://prefix/path
# Split the URI into protocol and path
match = URI_PATTERN.match(uri)
if not match:
raise ValueError(
f"Invalid URI format: {uri}. Expected protocol://path format."
)
protocol, path = match.groups()
# Check if the path starts with the prefix followed by a /
prefix_pattern = f"^{re.escape(prefix)}/(.*?)$"
path_match = re.match(prefix_pattern, path)
if not path_match:
return uri
# Return the URI without the prefix
return f"{protocol}{path_match.group(1)}"
else:
raise ValueError(f"Invalid prefix format: {prefix_format}")
# Return the URI without the prefix
return f"{protocol}{path_match.group(1)}"
def has_resource_prefix(
uri: str, prefix: str, prefix_format: Literal["protocol", "path"] | None = None
) -> bool:
def has_resource_prefix(uri: str, prefix: str) -> bool:
"""Check if a resource URI has a specific prefix.
Args:
@ -2715,16 +2641,10 @@ def has_resource_prefix(
True if the URI has the specified prefix, False otherwise
Examples:
With new style:
```python
has_resource_prefix("resource://prefix/path/to/resource", "prefix")
True
```
With legacy style:
```python
has_resource_prefix("prefix+resource://path/to/resource", "prefix")
True
```
With other path:
```python
has_resource_prefix("resource://other/path/to/resource", "prefix")
@ -2737,28 +2657,13 @@ def has_resource_prefix(
if not prefix:
return False
# Get the server settings to check for legacy format preference
# Split the URI into protocol and path
match = URI_PATTERN.match(uri)
if not match:
raise ValueError(f"Invalid URI format: {uri}. Expected protocol://path format.")
if prefix_format is None:
prefix_format = fastmcp.settings.resource_prefix_format
_, path = match.groups()
if prefix_format == "protocol":
# Legacy style: prefix+protocol://path
legacy_prefix = f"{prefix}+"
return uri.startswith(legacy_prefix)
elif prefix_format == "path":
# New style: protocol://prefix/path
# Split the URI into protocol and path
match = URI_PATTERN.match(uri)
if not match:
raise ValueError(
f"Invalid URI format: {uri}. Expected protocol://path format."
)
_, path = match.groups()
# Check if the path starts with the prefix followed by a /
prefix_pattern = f"^{re.escape(prefix)}/"
return bool(re.match(prefix_pattern, path))
else:
raise ValueError(f"Invalid prefix format: {prefix_format}")
# Check if the path starts with the prefix followed by a /
prefix_pattern = f"^{re.escape(prefix)}/"
return bool(re.match(prefix_pattern, path))

View file

@ -155,19 +155,6 @@ class Settings(BaseSettings):
),
] = True
resource_prefix_format: Annotated[
Literal["protocol", "path"],
Field(
description=inspect.cleandoc(
"""
When perfixing a resource URI, either use path formatting (resource://prefix/path)
or protocol formatting (prefix+resource://path). Protocol formatting was the default in FastMCP < 2.4;
path formatting is current default.
"""
),
),
] = "path"
client_init_timeout: Annotated[
float | None,
Field(

View file

@ -1,105 +0,0 @@
"""Tests for legacy resource prefix behavior."""
import pytest
from fastmcp import Client, FastMCP
from fastmcp.server.server import (
add_resource_prefix,
has_resource_prefix,
remove_resource_prefix,
)
from fastmcp.utilities.tests import temporary_settings
# reset deprecation warnings for this module
pytestmark = pytest.mark.filterwarnings("default::DeprecationWarning")
class TestLegacyResourcePrefixes:
"""Test the legacy resource prefix behavior."""
def test_add_resource_prefix_legacy(self):
"""Test that add_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
with temporary_settings(resource_prefix_format="protocol"):
result = add_resource_prefix("resource://path/to/resource", "prefix")
assert result == "prefix+resource://path/to/resource"
# Empty prefix should return the original URI
result = add_resource_prefix("resource://path/to/resource", "")
assert result == "resource://path/to/resource"
def test_remove_resource_prefix_legacy(self):
"""Test that remove_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
with temporary_settings(resource_prefix_format="protocol"):
result = remove_resource_prefix(
"prefix+resource://path/to/resource", "prefix"
)
assert result == "resource://path/to/resource"
# URI without the prefix should be returned as is
result = remove_resource_prefix("resource://path/to/resource", "prefix")
assert result == "resource://path/to/resource"
# Empty prefix should return the original URI
result = remove_resource_prefix("resource://path/to/resource", "")
assert result == "resource://path/to/resource"
def test_has_resource_prefix_legacy(self):
"""Test that has_resource_prefix uses the legacy format when resource_prefix_format is 'protocol'."""
with temporary_settings(resource_prefix_format="protocol"):
result = has_resource_prefix("prefix+resource://path/to/resource", "prefix")
assert result is True
result = has_resource_prefix("resource://path/to/resource", "prefix")
assert result is False
# Empty prefix should always return False
result = has_resource_prefix("resource://path/to/resource", "")
assert result is False
async def test_mount_with_legacy_prefixes():
"""Test mounting a server with legacy resource prefixes."""
with temporary_settings(resource_prefix_format="protocol"):
main_server = FastMCP("MainServer")
sub_server = FastMCP("SubServer")
@sub_server.resource("resource://test")
def get_test():
return "test content"
# Mount the server with a prefix (using old argument order for this legacy test)
with pytest.warns(DeprecationWarning, match="Mount prefixes are now optional"):
main_server.mount("sub", sub_server) # type: ignore[arg-type]
# Check that the resource is prefixed using the legacy format
resources = await main_server.get_resources()
# In legacy format, the key would be "sub+resource://test"
assert "sub+resource://test" in resources
# Test accessing the resource through client
async with Client(main_server) as client:
result = await client.read_resource("sub+resource://test")
# Different content types might be returned, but we just want to verify we got something
assert len(result) > 0
async def test_import_server_with_legacy_prefixes():
"""Test importing a server with legacy resource prefixes."""
with temporary_settings(resource_prefix_format="protocol"):
main_server = FastMCP("MainServer")
sub_server = FastMCP("SubServer")
@sub_server.resource("resource://test")
def get_test():
return "test content"
# Import the server with a prefix (using old argument order for this legacy test)
with pytest.warns(DeprecationWarning, match="Import prefixes are now optional"):
await main_server.import_server("sub", sub_server) # type: ignore[arg-type]
# Check that the resource is prefixed using the legacy format
resources = await main_server.get_resources()
# In legacy format, the key would be "sub+resource://test"
assert "sub+resource://test" in resources

View file

@ -178,7 +178,6 @@ class TestDeprecatedServerInitKwargs:
on_duplicate_tools="warn",
on_duplicate_resources="error",
on_duplicate_prompts="replace",
resource_prefix_format="path",
mask_error_details=True,
)

View file

@ -391,25 +391,6 @@ async def test_import_with_proxy_resource_templates():
assert content["email"] == "john@example.com"
async def test_import_invalid_resource_prefix():
main_app = FastMCP("MainApp")
api_app = FastMCP("APIApp")
# This test doesn't apply anymore with the new prefix format since we're not validating
# the protocol://prefix/path format
# Just import the server to maintain test coverage without deprecated parameters
await main_app.import_server(api_app, "api")
async def test_import_invalid_resource_separator():
main_app = FastMCP("MainApp")
api_app = FastMCP("APIApp")
# This test is for maintaining coverage for importing with prefixes
# We no longer pass the deprecated resource_separator parameter
await main_app.import_server(api_app, "api")
async def test_import_with_no_prefix():
"""Test importing a server without providing a prefix."""
main_app = FastMCP("MainApp")

View file

@ -66,22 +66,6 @@ class TestBasicMount:
result = await client.call_tool("sub_greet", {"name": "World"})
assert result.data == "Hello, World!"
async def test_mount_invalid_resource_prefix(self):
main_app = FastMCP("MainApp")
api_app = FastMCP("APIApp")
# This test doesn't apply anymore with the new prefix format
# just mount the server to maintain test coverage
main_app.mount(api_app, "api:sub")
async def test_mount_invalid_resource_separator(self):
main_app = FastMCP("MainApp")
api_app = FastMCP("APIApp")
# This test doesn't apply anymore with the new prefix format
# Mount without deprecated parameters
main_app.mount(api_app, "api")
@pytest.mark.parametrize("prefix", ["", None])
async def test_mount_with_no_prefix(self, prefix):
main_app = FastMCP("MainApp")

View file

@ -1,65 +0,0 @@
"""Tests for different resource prefix formats in server mounting and importing."""
from fastmcp import FastMCP
async def test_resource_prefix_format_in_constructor():
"""Test that the resource_prefix_format parameter is respected in the constructor."""
server_path = FastMCP("PathFormat", resource_prefix_format="path")
server_protocol = FastMCP("ProtocolFormat", resource_prefix_format="protocol")
# Check that the format is stored correctly
assert server_path.resource_prefix_format == "path"
assert server_protocol.resource_prefix_format == "protocol"
# Register resources
@server_path.resource("resource://test")
def get_test_path():
return "test content"
@server_protocol.resource("resource://test")
def get_test_protocol():
return "test content"
# Create mount servers
main_server_path = FastMCP("MainPath", resource_prefix_format="path")
main_server_protocol = FastMCP("MainProtocol", resource_prefix_format="protocol")
# Mount the servers
main_server_path.mount(server_path, "sub")
main_server_protocol.mount(server_protocol, "sub")
# Check that the resources are prefixed correctly
path_resources = await main_server_path.get_resources()
protocol_resources = await main_server_protocol.get_resources()
# Path format should be resource://sub/test
assert "resource://sub/test" in path_resources
# Protocol format should be sub+resource://test
assert "sub+resource://test" in protocol_resources
async def test_resource_prefix_format_in_import_server():
"""Test that the resource_prefix_format parameter is respected in import_server."""
server = FastMCP("TestServer")
@server.resource("resource://test")
def get_test():
return "test content"
# Import with path format
main_server_path = FastMCP("MainPath", resource_prefix_format="path")
await main_server_path.import_server(server, "sub")
# Import with protocol format
main_server_protocol = FastMCP("MainProtocol", resource_prefix_format="protocol")
await main_server_protocol.import_server(server, "sub")
# Check that the resources are prefixed correctly
path_resources = await main_server_path._resource_manager.get_resources()
protocol_resources = await main_server_protocol._resource_manager.get_resources()
# Path format should be resource://sub/test
assert "resource://sub/test" in path_resources
# Protocol format should be sub+resource://test
assert "sub+resource://test" in protocol_resources

View file

@ -1287,20 +1287,11 @@ class TestResourcePrefixMounting:
"""Test that resource prefix utility functions correctly match and strip resource prefixes."""
from fastmcp.server.server import has_resource_prefix, remove_resource_prefix
# Create a basic server to get the default resource prefix format
server = FastMCP()
# Test matching
assert (
has_resource_prefix(uri, prefix, server.resource_prefix_format)
== expected_match
)
assert has_resource_prefix(uri, prefix) == expected_match
# Test stripping
assert (
remove_resource_prefix(uri, prefix, server.resource_prefix_format)
== expected_strip
)
assert remove_resource_prefix(uri, prefix) == expected_strip
async def test_import_server_with_new_prefix_format(self):
"""Test that import_server correctly uses the new prefix format."""