diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index bf2a29d18..7e245b4ec 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -131,14 +131,8 @@ class FastMCP(Generic[LifespanResultT]): mask_error_details: bool | None = None, tools: list[Tool | Callable[..., Any]] | None = None, dependencies: list[str] | None = None, - include_tags: set[str] - | set[tuple[str, ...]] - | set[str | tuple[str, ...]] - | None = None, - exclude_tags: set[str] - | set[tuple[str, ...]] - | set[str | tuple[str, ...]] - | None = None, + include_tags: set[str] | None = None, + exclude_tags: set[str] | None = None, # --- # --- # --- The following arguments are DEPRECATED --- @@ -1681,10 +1675,8 @@ class FastMCP(Generic[LifespanResultT]): • If the component's enabled property is False, always return False. • If both include_tags and exclude_tags are None, return True. • If exclude_tags is provided, check each exclude tag: - - If the exclude tag is a tuple, all tags in the tuple must be present in the input tags to exclude. - If the exclude tag is a string, it must be present in the input tags to exclude. • If include_tags is provided, check each include tag: - - If the include tag is a tuple, all tags in the tuple must be present in the input tags to include. - If the include tag is a string, it must be present in the input tags to include. • If include_tags is provided and none of the include tags match, return False. • If include_tags is not provided, return True. @@ -1696,26 +1688,16 @@ class FastMCP(Generic[LifespanResultT]): return True if self.exclude_tags is not None: - for etag in self.exclude_tags: - if isinstance(etag, tuple): - if all(et in component.tags for et in etag): - return False - else: - if etag in component.tags: - return False + if any(etag in component.tags for etag in self.exclude_tags): + return False if self.include_tags is not None: - for itag in self.include_tags: - if isinstance(itag, tuple): - if all(it in component.tags for it in itag): - return True - else: - if itag in component.tags: - return True + if any(itag in component.tags for itag in self.include_tags): + return True + else: + return False - return False - else: - return True + return True class MountedServer: diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py index 941c5b319..8d6780257 100644 --- a/src/fastmcp/settings.py +++ b/src/fastmcp/settings.py @@ -235,44 +235,27 @@ class Settings(BaseSettings): ] = None include_tags: Annotated[ - set[str] | set[tuple[str, ...]] | set[str | tuple[str, ...]] | None, + set[str] | None, Field( default=None, description=inspect.cleandoc( """ If provided, only components that match these tags will be - exposed to clients. This can be a set of tags or tuples of tags. - A component is considered to match if ANY of its tags match ANY - of the tags in the set, or if any combination of its tags match - ALL of the tags in any tuple in the set. - - For example, if include_tags is set to {"tag1", ("tag2", - "tag3")}, then a component with tags {"tag1", "tag4"} or - {"tag2", "tag3", "tag4"} will be included, but a component with - tags {"tag2", "tag4"} will not be included. + exposed to clients. A component is considered to match if ANY of + its tags match ANY of the tags in the set. """ ), ), ] = None exclude_tags: Annotated[ - set[str] | set[tuple[str, ...]] | set[str | tuple[str, ...]] | None, + set[str] | None, Field( default=None, description=inspect.cleandoc( """ If provided, components that match these tags will be excluded - from the server. This can be a set of tags or tuples of tags. - This is applied after include_tags, so if a component matches - both include_tags and exclude_tags, it will be excluded. - - A component is considered to match if ANY of its tags match ANY - of the tags in the set, or if any combination of its tags match - ALL of the tags in any tuple in the set. - - For example, if exclude_tags is set to {"tag1", ("tag2", - "tag3")}, then a component with tags {"tag1", "tag4"} or - {"tag2", "tag3", "tag4"} will be excluded, but a component with - tags {"tag2", "tag4"} will not be excluded. + from the server. A component is considered to match if ANY of + its tags match ANY of the tags in the set. """ ), ), diff --git a/tests/server/test_server.py b/tests/server/test_server.py index 7a0ee3e4e..dd4719a6e 100644 --- a/tests/server/test_server.py +++ b/tests/server/test_server.py @@ -1261,20 +1261,6 @@ class TestShouldIncludeComponent: result = mcp._should_enable_component(tool) assert result is True - def test_exclude_tuple_all_present_returns_false(self): - """Test that when all tags in exclude tuple are present, returns False.""" - tool = Tool(name="test_tool", tags={"tag1", "tag2", "tag3"}, parameters={}) - mcp = FastMCP(tools=[tool], exclude_tags={("tag1", "tag2")}) - result = mcp._should_enable_component(tool) - assert result is False - - def test_exclude_tuple_partial_present_returns_true(self): - """Test that when only some tags in exclude tuple are present, returns True.""" - tool = Tool(name="test_tool", tags={"tag1", "tag3"}, parameters={}) - mcp = FastMCP(tools=[tool], exclude_tags={("tag1", "tag2")}) - result = mcp._should_enable_component(tool) - assert result is True - def test_multiple_exclude_tags_any_match_returns_false(self): """Test that when any exclude tag matches, returns False.""" tool = Tool(name="test_tool", tags={"tag1", "tag2", "tag3"}, parameters={}) @@ -1300,20 +1286,6 @@ class TestShouldIncludeComponent: result = mcp._should_enable_component(tool) assert result is False - def test_include_tuple_all_present_returns_true(self): - """Test that when all tags in include tuple are present, returns True.""" - tool = Tool(name="test_tool", tags={"tag1", "tag2", "tag3"}, parameters={}) - mcp = FastMCP(tools=[tool], include_tags={("tag1", "tag2")}) - result = mcp._should_enable_component(tool) - assert result is True - - def test_include_tuple_partial_present_returns_false(self): - """Test that when only some tags in include tuple are present, returns False.""" - tool = Tool(name="test_tool", tags={"tag1", "tag3"}, parameters={}) - mcp = FastMCP(tools=[tool], include_tags={("tag1", "tag2")}) - result = mcp._should_enable_component(tool) - assert result is False - def test_multiple_include_tags_any_match_returns_true(self): """Test that when any include tag matches, returns True.""" tool = Tool(name="test_tool", tags={"tag1", "tag2", "tag3"}, parameters={}) @@ -1339,79 +1311,6 @@ class TestShouldIncludeComponent: result = mcp._should_enable_component(tool) assert result is False - def test_mixed_string_and_tuple_exclude_tags(self): - """Test exclude tags with both string and tuple formats.""" - # Should be excluded because "tag1" is present - tool1 = Tool( - name="test_tool", tags={"tag1", "tag2", "tag3", "tag4"}, parameters={} - ) - mcp1 = FastMCP(tools=[tool1], exclude_tags={"tag1", ("tag2", "tag3")}) - result = mcp1._should_enable_component(tool1) - assert result is False - - # Remove tag1, should still be excluded because both tag2 and tag3 are present - tool2 = Tool(name="test_tool", tags={"tag2", "tag3", "tag4"}, parameters={}) - mcp2 = FastMCP(tools=[tool2], exclude_tags={"tag1", ("tag2", "tag3")}) - result = mcp2._should_enable_component(tool2) - assert result is False - - # Remove tag2, should not be excluded - tool3 = Tool( - name="test_tool", tags={"tag1_removed", "tag3", "tag4"}, parameters={} - ) - mcp3 = FastMCP(tools=[tool3], exclude_tags={("tag2", "tag3")}) - result = mcp3._should_enable_component(tool3) - assert result is True - - def test_mixed_string_and_tuple_include_tags(self): - """Test include tags with both string and tuple formats.""" - # Should be included because both tag1 and tag2 are present (tuple match) - tool1 = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={}) - mcp1 = FastMCP(tools=[tool1], include_tags={"not_present", ("tag1", "tag2")}) - result = mcp1._should_enable_component(tool1) - assert result is True - - # Should be included because tag1 is present (string match) - tool2 = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={}) - mcp2 = FastMCP( - tools=[tool2], include_tags={"tag1", ("not_present1", "not_present2")} - ) - result = mcp2._should_enable_component(tool2) - assert result is True - - # Should not be included because no conditions are met - tool3 = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={}) - mcp3 = FastMCP( - tools=[tool3], - include_tags={"not_present", ("not_present1", "not_present2")}, - ) - result = mcp3._should_enable_component(tool3) - assert result is False - - def test_complex_scenario_with_both_filters(self): - """Test complex scenario with both include and exclude filters.""" - # Should be excluded despite matching include conditions - tool1 = Tool( - name="test_tool", tags={"api", "read", "admin", "sensitive"}, parameters={} - ) - mcp1 = FastMCP( - tools=[tool1], - include_tags={"api", ("read", "admin")}, - exclude_tags={"sensitive"}, - ) - result = mcp1._should_enable_component(tool1) - assert result is False - - # Remove sensitive tag, should now be included - tool2 = Tool(name="test_tool", tags={"api", "read", "admin"}, parameters={}) - mcp2 = FastMCP( - tools=[tool2], - include_tags={"api", ("read", "admin")}, - exclude_tags={"sensitive"}, - ) - result = mcp2._should_enable_component(tool2) - assert result is True - def test_empty_include_exclude_sets(self): """Test behavior with empty include/exclude sets.""" # Empty include set means nothing matches @@ -1439,27 +1338,3 @@ class TestShouldIncludeComponent: mcp2 = FastMCP(tools=[tool2], exclude_tags={"bad_tag"}) result = mcp2._should_enable_component(tool2) assert result is True - - # Tuple filters with empty tags - tool3 = Tool(name="test_tool", tags=set(), parameters={}) - mcp3 = FastMCP(tools=[tool3], include_tags={("tag1", "tag2")}) - result = mcp3._should_enable_component(tool3) - assert result is False - - tool4 = Tool(name="test_tool", tags=set(), parameters={}) - mcp4 = FastMCP(tools=[tool4], exclude_tags={("tag1", "tag2")}) - result = mcp4._should_enable_component(tool4) - assert result is True - - def test_single_element_tuples(self): - """Test behavior with single-element tuples.""" - # Single-element tuple should behave like a string - tool1 = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={}) - mcp1 = FastMCP(tools=[tool1], include_tags={("tag1",)}) - result = mcp1._should_enable_component(tool1) - assert result is True - - tool2 = Tool(name="test_tool", tags={"tag1", "tag2"}, parameters={}) - mcp2 = FastMCP(tools=[tool2], exclude_tags={("tag1",)}) - result = mcp2._should_enable_component(tool2) - assert result is False diff --git a/tests/server/test_server_interactions.py b/tests/server/test_server_interactions.py index efd6de6e8..859918f59 100644 --- a/tests/server/test_server_interactions.py +++ b/tests/server/test_server_interactions.py @@ -143,13 +143,6 @@ class TestToolTags: tools = await client.list_tools() assert {t.name for t in tools} == {"tool_1"} - async def test_include_tags_tuple(self): - mcp = self.create_server(include_tags={("a", "b")}) - - async with Client(mcp) as client: - tools = await client.list_tools() - assert {t.name for t in tools} == {"tool_1"} - async def test_exclude_tags_all_tools(self): mcp = self.create_server(exclude_tags={"a", "b"}) @@ -164,13 +157,6 @@ class TestToolTags: tools = await client.list_tools() assert {t.name for t in tools} == {"tool_2"} - async def test_exclude_tags_tuple(self): - mcp = self.create_server(exclude_tags={("a", "b")}) - - async with Client(mcp) as client: - tools = await client.list_tools() - assert {t.name for t in tools} == {"tool_2"} - async def test_exclude_precedence(self): mcp = self.create_server(exclude_tags={"a"}, include_tags={"b"})