From c7c9b913273f31b456966a6dab99c55a60d85d17 Mon Sep 17 00:00:00 2001 From: Ronald de Wit Date: Sat, 8 Aug 2026 12:13:16 +0200 Subject: [PATCH] Omit unset values from Docker filters --- src/mcp_server_docker/server.py | 13 ++++++++++--- tests/test_server.py | 6 +++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/mcp_server_docker/server.py b/src/mcp_server_docker/server.py index 59e835a..917d668 100644 --- a/src/mcp_server_docker/server.py +++ b/src/mcp_server_docker/server.py @@ -72,6 +72,13 @@ def _client(ctx: Context[AppContext]) -> docker.DockerClient: return ctx.request_context.lifespan_context.docker +def _docker_filters(model: BaseModel | None) -> dict[str, Any] | None: + """Return Docker SDK filters without unset optional values.""" + if model is None: + return None + return model.model_dump(exclude_none=True) + + @asynccontextmanager async def lifespan(_: MCPServer[AppContext]) -> AsyncIterator[AppContext]: """Create and close the Docker client for one server lifetime.""" @@ -243,7 +250,7 @@ def list_containers( return [ docker_to_dict(container) for container in _client(ctx).containers.list( - all=all, filters=filters.model_dump() if filters else None + all=all, filters=_docker_filters(filters) ) ] @@ -495,7 +502,7 @@ def list_images( return [ docker_to_dict(image) for image in _client(ctx).images.list( - name=name, all=all, filters=filters.model_dump() if filters else None + name=name, all=all, filters=_docker_filters(filters) ) ] @@ -575,7 +582,7 @@ def list_networks( return [ docker_to_dict(network) for network in _client(ctx).networks.list( - filters=filters.model_dump() if filters else None + filters=_docker_filters(filters) ) ] diff --git a/tests/test_server.py b/tests/test_server.py index a6c64d0..b86254c 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -143,6 +143,8 @@ async def test_tools_have_flat_schemas_and_container_call_semantics( await client.call_tool("list_containers", {}) assert docker_client.call("containers.list") == {"all": False, "filters": None} + await client.call_tool("list_containers", {"filters": {}}) + assert docker_client.call("containers.list") == {"all": False, "filters": {}} await client.call_tool( "list_containers", {"all": True, "filters": {"label": ["a=b"]}} ) @@ -206,7 +208,7 @@ async def test_image_network_and_volume_call_semantics(server, docker_client): assert docker_client.call("images.list") == { "name": "alpine", "all": True, - "filters": {"dangling": True, "label": None}, + "filters": {"dangling": True}, } await client.call_tool("pull_image", {"repository": "alpine", "tag": "3.20"}) assert docker_client.call("images.pull") == ("alpine", {"tag": "3.20"}) @@ -223,6 +225,8 @@ async def test_image_network_and_volume_call_semantics(server, docker_client): } await client.call_tool("remove_image", {"image": "test", "force": True}) assert docker_client.call("images.remove") == {"image": "test", "force": True} + await client.call_tool("list_networks", {"filters": {}}) + assert docker_client.call("networks.list") == {"filters": {}} await client.call_tool("list_networks", {"filters": {"label": ["x=y"]}}) assert docker_client.call("networks.list") == {"filters": {"label": ["x=y"]}} await client.call_tool("create_network", {"name": "n", "internal": True})