Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions docs/python-sdk/fastmcp-mcp_config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
Infer the appropriate transport type from the given URL.


### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L324" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L345" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
update_config_file(file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes) -> None
Expand All @@ -57,7 +57,7 @@ worry about transforming server objects here.

## Classes

### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L134" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `StdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L155" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>


MCP server configuration for stdio transport.
Expand All @@ -67,19 +67,19 @@ This is the canonical configuration format for MCP servers using stdio transport

**Methods:**

#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L188" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
to_transport(self) -> StdioTransport
```

### `TransformingStdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L179" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `TransformingStdioMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L200" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>


A Stdio server with tool transforms.


### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `RemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L204" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>


MCP server configuration for HTTP/SSE transport.
Expand All @@ -89,19 +89,19 @@ This is the canonical configuration format for MCP servers using remote transpor

**Methods:**

#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L219" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `to_transport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L240" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
to_transport(self) -> StreamableHttpTransport | SSETransport
```

### `TransformingRemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L244" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `TransformingRemoteMCPServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L265" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>


A Remote server with tool transforms.


### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `MCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L276" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>


A configuration object for MCP Servers that conforms to the canonical MCP configuration format
Expand All @@ -113,7 +113,7 @@ For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class.

**Methods:**

#### `wrap_servers_at_root` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `wrap_servers_at_root` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L290" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]
Expand All @@ -122,7 +122,7 @@ wrap_servers_at_root(cls, values: dict[str, Any]) -> dict[str, Any]
If there's no mcpServers key but there are server configs at root, wrap them.


#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L282" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L303" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
add_server(self, name: str, server: MCPServerTypes) -> None
Expand All @@ -131,7 +131,7 @@ add_server(self, name: str, server: MCPServerTypes) -> None
Add or update a server in the configuration.


#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `from_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L308" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
from_dict(cls, config: dict[str, Any]) -> Self
Expand All @@ -140,7 +140,7 @@ from_dict(cls, config: dict[str, Any]) -> Self
Parse MCP configuration from dictionary format.


#### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L291" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `to_dict` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L312" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
to_dict(self) -> dict[str, Any]
Expand All @@ -149,7 +149,7 @@ to_dict(self) -> dict[str, Any]
Convert MCPConfig to dictionary format, preserving all fields.


#### `write_to_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `write_to_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L316" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
write_to_file(self, file_path: Path) -> None
Expand All @@ -158,7 +158,7 @@ write_to_file(self, file_path: Path) -> None
Write configuration to JSON file.


#### `from_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `from_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L322" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
from_file(cls, file_path: Path) -> Self
Expand All @@ -167,7 +167,7 @@ from_file(cls, file_path: Path) -> Self
Load configuration from JSON file.


### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>


Canonical MCP configuration format.
Expand All @@ -178,7 +178,7 @@ The format is designed to be client-agnostic and extensible for future use cases

**Methods:**

#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L319" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L340" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python
add_server(self, name: str, server: CanonicalMCPServerTypes) -> None
Expand Down
23 changes: 22 additions & 1 deletion src/fastmcp/mcp_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def infer_transport_type_from_url(
class _TransformingMCPServerMixin(FastMCPBaseModel):
"""A mixin that enables wrapping an MCP Server with tool transforms."""

tools: dict[str, ToolTransformConfig] = Field(...)
tools: dict[str, ToolTransformConfig] = Field(default_factory=dict)
"""The multi-tool transform to apply to the tools."""

include_tags: set[str] | None = Field(
Expand All @@ -89,6 +89,27 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
description="The tags to exclude in the proxy.",
)

@model_validator(mode="before")
@classmethod
def _require_at_least_one_transform_field(
cls, values: dict[str, Any]
) -> dict[str, Any]:
"""Reject if none of the transforming fields are set.

This ensures that plain server configs (without tools, include_tags,
or exclude_tags) fall through to the base server types during union
validation, avoiding unnecessary proxy wrapping.
"""
if isinstance(values, dict):
has_tools = bool(values.get("tools"))
has_include = values.get("include_tags") is not None
has_exclude = values.get("exclude_tags") is not None
if not (has_tools or has_include or has_exclude):
raise ValueError(
"At least one of 'tools', 'include_tags', or 'exclude_tags' is required"
)
return values

def _to_server_and_underlying_transport(
self,
server_name: str | None = None,
Expand Down
54 changes: 53 additions & 1 deletion tests/test_mcp_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,25 @@ def test_parse_mcpservers_discriminator():
"args": ["hello"],
},
"test_server_two": {"command": "echo", "args": ["hello"], "tools": {}},
"test_server_three": {
"command": "echo",
"args": ["hello"],
"include_tags": ["my_tag"],
},
}

mcp_config = MCPConfig.from_dict(config)

test_server: MCPServerTypes = mcp_config.mcpServers["test_server"]
assert isinstance(test_server, StdioMCPServer)

# Empty tools dict with no tags is not a meaningful transform
test_server_two: MCPServerTypes = mcp_config.mcpServers["test_server_two"]
assert isinstance(test_server_two, TransformingStdioMCPServer)
assert isinstance(test_server_two, StdioMCPServer)

# include_tags alone triggers transforming type
test_server_three: MCPServerTypes = mcp_config.mcpServers["test_server_three"]
assert isinstance(test_server_three, TransformingStdioMCPServer)

canonical_mcp_config = CanonicalMCPConfig.from_dict(config)

Expand Down Expand Up @@ -738,6 +748,48 @@ def subtract(a: int, b: int) -> int:
assert "test_2_subtract" in tools_by_name


@pytest.mark.flaky(retries=3)
async def test_single_server_config_include_tags_filtering(tmp_path: Path):
"""include_tags should filter tools even with a single server in the config."""
server_script = inspect.cleandoc("""
from fastmcp import FastMCP

mcp = FastMCP()

@mcp.tool(tags={"keep"})
def add(a: int, b: int) -> int:
return a + b

@mcp.tool
def subtract(a: int, b: int) -> int:
return a - b

if __name__ == '__main__':
mcp.run()
""")

script_path = tmp_path / "test.py"
script_path.write_text(server_script)

config = {
"mcpServers": {
"test": {
"command": "python",
"args": [str(script_path)],
"include_tags": ["keep"],
},
}
}

client = Client(config)

async with client:
tools = await client.list_tools()
tool_names = {tool.name for tool in tools}
assert "add" in tool_names
assert "subtract" not in tool_names


async def test_multi_client_with_elicitation(tmp_path: Path):
"""
Tests that elicitation is properly forwarded to the ultimate client.
Expand Down