Skip to content

Commit a4ac3cd

Browse files
authored
fix: config dict should not be mutated directly (#162)
1 parent 9b6dcab commit a4ac3cd

3 files changed

Lines changed: 47 additions & 3 deletions

File tree

src/deepset_mcp/api/transport.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77
from collections.abc import AsyncIterator
88
from contextlib import AbstractAsyncContextManager, asynccontextmanager
9+
from copy import deepcopy
910
from dataclasses import dataclass
1011
from typing import Any, Generic, Literal, Protocol, TypeVar, cast, overload
1112

@@ -198,8 +199,10 @@ def __init__(
198199
config : dict, optional
199200
Configuration for httpx.AsyncClient, e.g., {'timeout': 10.0}
200201
"""
201-
config = config or {}
202-
# Ensure auth header
202+
# We deepcopy the config so that we don't mutate it when used for subsequent initializations
203+
config = deepcopy(config) or {}
204+
205+
# Merge auth and other config headers
203206
headers = config.pop("headers", {})
204207
headers.setdefault("Authorization", f"Bearer {api_key}")
205208
# Build client kwargs

src/deepset_mcp/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@
3838
DEEPSET_DOCS_DEFAULT_SHARE_URL = "https://cloud.deepset.ai/shared_prototypes?share_token=prototype_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODM0MjE0OTguNTk5LCJhdWQiOiJleHRlcm5hbCB1c2VyIiwiaXNzIjoiZEMiLCJ3b3Jrc3BhY2VfaWQiOiI4YzI0ZjExMi1iMjljLTQ5MWMtOTkzOS1hZTkxMDRhNTQyMWMiLCJ3b3Jrc3BhY2VfbmFtZSI6ImRjLWRvY3MtY29udGVudCIsIm9yZ2FuaXphdGlvbl9pZCI6ImNhOWYxNGQ0LWMyYzktNDYwZC04ZDI2LWY4Y2IwYWNhMDI0ZiIsInNoYXJlX2lkIjoiY2Y3MTA3ODAtOThmNi00MzlmLThiNzYtMmMwNDkyODNiMDZhIiwibG9naW5fcmVxdWlyZWQiOmZhbHNlfQ.5j6DCNRQ1_KB8lhIJqHyw2hBIleEW1_Y_UBuH6MTYY0"
3939
DOCS_SEARCH_TOOL_NAME = "search_docs"
4040

41-
DEFAULT_CLIENT_HEADER = {"headers": {"user-agent": f"deepset-mcp/{__version__}"}}
41+
DEFAULT_CLIENT_HEADER = {"headers": {"User-Agent": f"deepset-mcp/{__version__}"}}

test/unit/api/test_transport.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,44 @@ async def test_close(self, transport: AsyncTransport, mock_httpx_client: AsyncMo
229229
await transport.close()
230230

231231
mock_httpx_client.aclose.assert_called_once()
232+
233+
async def test_config_not_mutated_on_repeated_initialization(self) -> None:
234+
"""Test that config dict is not mutated when creating multiple AsyncTransport instances."""
235+
# Create a config with headers
236+
original_config = {
237+
"timeout": 30.0,
238+
"headers": {"Custom-Header": "value", "Another-Header": "another-value"},
239+
"follow_redirects": True,
240+
}
241+
242+
with patch("httpx.AsyncClient") as mock_client_class:
243+
# First initialization - should not mutate original config
244+
AsyncTransport(base_url="https://api.example.com", api_key="test-key-1", config=original_config)
245+
246+
# Verify the config still has headers after first initialization
247+
assert "headers" in original_config
248+
assert original_config["headers"] == {"Custom-Header": "value", "Another-Header": "another-value"}
249+
250+
# Second initialization with same config - should still work
251+
AsyncTransport(base_url="https://api.example.com", api_key="test-key-2", config=original_config)
252+
253+
# Verify the config still has headers after second initialization
254+
assert "headers" in original_config
255+
assert original_config["headers"] == {"Custom-Header": "value", "Another-Header": "another-value"}
256+
assert original_config["timeout"] == 30.0
257+
assert original_config["follow_redirects"] is True
258+
259+
# Verify both clients were created with the expected headers
260+
assert mock_client_class.call_count == 2
261+
262+
# Check first call
263+
first_call_args = mock_client_class.call_args_list[0][1]
264+
assert first_call_args["headers"]["Authorization"] == "Bearer test-key-1"
265+
assert first_call_args["headers"]["Custom-Header"] == "value"
266+
assert first_call_args["headers"]["Another-Header"] == "another-value"
267+
268+
# Check second call
269+
second_call_args = mock_client_class.call_args_list[1][1]
270+
assert second_call_args["headers"]["Authorization"] == "Bearer test-key-2"
271+
assert second_call_args["headers"]["Custom-Header"] == "value"
272+
assert second_call_args["headers"]["Another-Header"] == "another-value"

0 commit comments

Comments
 (0)