mcp-haystack 1.5.1__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/CHANGELOG.md +33 -1
  2. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/PKG-INFO +3 -4
  3. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/pyproject.toml +2 -4
  4. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/src/haystack_integrations/tools/mcp/mcp_tool.py +5 -3
  5. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/src/haystack_integrations/tools/mcp/mcp_toolset.py +5 -2
  6. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/test_mcp_tool.py +15 -1
  7. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/test_mcp_toolset.py +20 -0
  8. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/.gitignore +0 -0
  9. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/LICENSE.txt +0 -0
  10. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/README.md +0 -0
  11. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/examples/mcp_client.py +0 -0
  12. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/examples/mcp_filtered_tools.py +0 -0
  13. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/examples/mcp_server.py +0 -0
  14. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/examples/mcp_stdio_client.py +0 -0
  15. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/examples/mcp_stdio_toolset.py +0 -0
  16. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/examples/time_pipeline.py +0 -0
  17. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/examples/time_pipeline_toolset.py +0 -0
  18. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/pydoc/config_docusaurus.yml +0 -0
  19. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/src/haystack_integrations/tools/mcp/__init__.py +0 -0
  20. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/src/haystack_integrations/tools/mcp/compatibility_layer.py +0 -0
  21. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/src/haystack_integrations/tools/py.typed +0 -0
  22. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/__init__.py +0 -0
  23. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/conftest.py +0 -0
  24. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/mcp_memory_transport.py +0 -0
  25. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/mcp_servers_fixtures.py +0 -0
  26. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/test_compatibility_layer.py +0 -0
  27. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/test_mcp_integration.py +0 -0
  28. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/test_mcp_server_info.py +0 -0
  29. {mcp_haystack-1.5.1 → mcp_haystack-2.0.0}/tests/test_mcp_timeout_reconnection.py +0 -0
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/mcp-v1.5.2] - 2026-10-07
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - MCP - warm_up must reopen connections after close (#4065)
8
+
9
+ ### ⚙️ CI
10
+
11
+ - Improve changelog generation; fix existing changelogs (#3883)
12
+
13
+
14
+ ## [integrations/mcp-v1.5.1] - 2026-09-01
15
+
16
+ ### 🚀 Features
17
+
18
+ - Support MCP Python SDK v2 - compatibility mode (#3878)
19
+
20
+ ### 🐛 Bug Fixes
21
+
22
+ - Standardize license files (#3771)
23
+
24
+ ### 🧪 Testing
25
+
26
+ - MCP - refactor test_toolset_combination for Toolset.__add__ removal (#3877)
27
+
28
+ ### 🧹 Chores
29
+
30
+ - Re-standardize READMEs (#3768)
31
+
32
+
3
33
  ## [integrations/mcp-v1.4.1] - 2026-07-29
4
34
 
5
35
  ### 🐛 Bug Fixes
@@ -103,7 +133,7 @@
103
133
 
104
134
  - Add MCPTool/MCPToolset warm_up (#2384)
105
135
 
106
- ## [integrations/mcp-v0.8.0] - 2025-10-14
136
+ ## [integrations/mcp-v0.0.8] - 2025-10-14
107
137
 
108
138
  ### 🐛 Bug Fixes
109
139
 
@@ -160,6 +190,8 @@
160
190
 
161
191
  - Align core-integrations Hatch scripts (#1898)
162
192
 
193
+ ## [integrations/mcp-v0.3.1] - 2025-05-28
194
+
163
195
  ### 🌀 Miscellaneous
164
196
 
165
197
  - App pins for MCP (#1845)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcp-haystack
3
- Version: 1.5.1
3
+ Version: 2.0.0
4
4
  Summary: Haystack integration for Model Context Protocol (MCP)
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/mcp#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -12,14 +12,13 @@ Keywords: Haystack,MCP,Model Context Protocol
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
20
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
- Requires-Python: >=3.10
22
- Requires-Dist: exceptiongroup
21
+ Requires-Python: >=3.11
23
22
  Requires-Dist: haystack-ai>=2.23.0
24
23
  Requires-Dist: mcp>=1.24.0
25
24
  Description-Content-Type: text/markdown
@@ -7,7 +7,7 @@ name = "mcp-haystack"
7
7
  dynamic = ["version"]
8
8
  description = "Haystack integration for Model Context Protocol (MCP)"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.11"
11
11
  license = "Apache-2.0"
12
12
  keywords = [
13
13
  "MCP",
@@ -21,17 +21,16 @@ classifiers = [
21
21
  "License :: OSI Approved :: Apache Software License",
22
22
  "Development Status :: 4 - Beta",
23
23
  "Programming Language :: Python",
24
- "Programming Language :: Python :: 3.10",
25
24
  "Programming Language :: Python :: 3.11",
26
25
  "Programming Language :: Python :: 3.12",
27
26
  "Programming Language :: Python :: 3.13",
28
27
  "Programming Language :: Python :: 3.14",
28
+ "Programming Language :: Python :: 3.15",
29
29
  "Programming Language :: Python :: Implementation :: CPython",
30
30
  ]
31
31
  dependencies = [
32
32
  "mcp>=1.24.0",
33
33
  "haystack-ai>=2.23.0",
34
- "exceptiongroup", # Backport of ExceptionGroup for Python < 3.11
35
34
  ]
36
35
 
37
36
  [project.urls]
@@ -88,7 +87,6 @@ disallow_incomplete_defs = true
88
87
 
89
88
 
90
89
  [tool.ruff]
91
- target-version = "py310"
92
90
  line-length = 120
93
91
 
94
92
  [tool.ruff.lint]
@@ -17,7 +17,6 @@ from contextlib import AsyncExitStack
17
17
  from dataclasses import dataclass, fields
18
18
  from typing import Any, cast
19
19
 
20
- from exceptiongroup import ExceptionGroup
21
20
  from haystack import logging
22
21
  from haystack.core.serialization import generate_qualified_class_name, import_class_by_name
23
22
  from haystack.tools import Tool
@@ -1121,7 +1120,7 @@ class MCPTool(Tool):
1121
1120
  return _extract_first_text_element(result)
1122
1121
 
1123
1122
  return result
1124
- except asyncio.TimeoutError as e:
1123
+ except TimeoutError as e:
1125
1124
  message = f"Tool invocation timed out after {self._invocation_timeout} seconds"
1126
1125
  raise TimeoutError(message) from e
1127
1126
  except Exception as e:
@@ -1155,7 +1154,7 @@ class MCPTool(Tool):
1155
1154
  return set(properties.keys())
1156
1155
 
1157
1156
  def warm_up(self) -> None:
1158
- """Connect and fetch the tool schema if eager_connect is turned off."""
1157
+ """Connect and fetch the tool schema unless already connected."""
1159
1158
  with self._lock:
1160
1159
  if self._client is not None:
1161
1160
  return
@@ -1266,6 +1265,9 @@ class MCPTool(Tool):
1266
1265
  self._worker.stop()
1267
1266
  except Exception as e:
1268
1267
  logger.debug(f"TOOL: Error during synchronous worker stop: {e!s}")
1268
+ finally:
1269
+ self._client = None
1270
+ self._worker = None
1269
1271
 
1270
1272
  def __del__(self) -> None:
1271
1273
  """Cleanup resources when the tool is garbage collected."""
@@ -7,7 +7,6 @@ from collections.abc import Callable
7
7
  from typing import Any, cast
8
8
  from urllib.parse import urlparse
9
9
 
10
- from exceptiongroup import ExceptionGroup
11
10
  from haystack import logging
12
11
  from haystack.core.serialization import generate_qualified_class_name, import_class_by_name
13
12
  from haystack.tools import Tool, Toolset
@@ -244,6 +243,7 @@ class MCPToolset(Toolset):
244
243
  self.outputs_to_state = outputs_to_state or {}
245
244
  self.outputs_to_string = outputs_to_string or {}
246
245
  self._warmup_called = False
246
+ self._worker: _MCPClientSessionManager | None = None
247
247
 
248
248
  if not eager_connect:
249
249
  # Do not connect during validation; expose a toolset with one fake tool to pass validation
@@ -261,7 +261,7 @@ class MCPToolset(Toolset):
261
261
 
262
262
  def warm_up(self) -> None:
263
263
  """
264
- Connect and load tools when eager_connect is turned off.
264
+ Connect and load tools unless already connected.
265
265
 
266
266
  This method is automatically called by `Agent.warm_up()` and `Pipeline.warm_up()`.
267
267
  You can also call it directly before using the toolset to ensure all tool schemas
@@ -511,6 +511,9 @@ class MCPToolset(Toolset):
511
511
  self._worker.stop()
512
512
  except Exception as e:
513
513
  logger.debug(f"TOOLSET: error during worker stop: {e!s}")
514
+ finally:
515
+ self._worker = None
516
+ self._warmup_called = False
514
517
 
515
518
  def __del__(self) -> None:
516
519
  self.close()
@@ -6,7 +6,6 @@ from types import SimpleNamespace
6
6
  from unittest.mock import AsyncMock, MagicMock, patch
7
7
 
8
8
  import pytest
9
- from exceptiongroup import ExceptionGroup
10
9
  from haystack.components.agents import Agent
11
10
  from haystack.components.generators.chat import OpenAIChatGenerator
12
11
  from haystack.core.pipeline import Pipeline
@@ -17,6 +16,7 @@ from haystack.tools.from_function import tool
17
16
  from haystack_integrations.tools.mcp import (
18
17
  MCPTool,
19
18
  MCPToolNotFoundError,
19
+ SSEServerInfo,
20
20
  StdioServerInfo,
21
21
  )
22
22
  from haystack_integrations.tools.mcp.mcp_tool import (
@@ -365,6 +365,20 @@ class TestMCPTool:
365
365
  assert "b" in tool.parameters["properties"]
366
366
  assert mock_connect.call_count == 1
367
367
 
368
+ @pytest.mark.parametrize("eager_connect", [False, True])
369
+ def test_mcp_tool_reopens_after_close(self, eager_connect, mcp_calculator_server, mcp_tool_cleanup):
370
+ port = mcp_calculator_server("sse")
371
+ server_info = SSEServerInfo(url=f"http://127.0.0.1:{port}/sse", max_retries=0)
372
+ tool = mcp_tool_cleanup(MCPTool(name="add", server_info=server_info, eager_connect=eager_connect))
373
+
374
+ tool.warm_up()
375
+ assert json.loads(tool.invoke(a=2, b=3))["content"][0]["text"] == "5"
376
+
377
+ tool.close()
378
+
379
+ tool.warm_up()
380
+ assert json.loads(tool.invoke(a=2, b=3))["content"][0]["text"] == "5"
381
+
368
382
  @pytest.mark.asyncio
369
383
  async def test_mcp_tool_ainvoke_matches_invoke_with_outputs_to_state(self, mcp_tool_cleanup):
370
384
  """Test that sync and async invocation paths return the same parsed state output."""
@@ -195,6 +195,26 @@ class TestMCPToolset:
195
195
  toolset.warm_up()
196
196
  assert [tool.name for tool in toolset.tools] == warmed_tool_names
197
197
 
198
+ @pytest.mark.parametrize("eager_connect", [False, True])
199
+ async def test_mcp_toolset_reopens_after_close(self, eager_connect, mcp_calculator_server, mcp_tool_cleanup):
200
+ port = mcp_calculator_server("sse")
201
+ server_info = SSEServerInfo(url=f"http://127.0.0.1:{port}/sse", max_retries=0)
202
+ toolset = mcp_tool_cleanup(MCPToolset(server_info=server_info, eager_connect=eager_connect))
203
+
204
+ toolset.warm_up()
205
+ assert sorted(tool.name for tool in toolset.tools) == ["add", "subtract"]
206
+ for tool in toolset.tools:
207
+ expected = "5" if tool.name == "add" else "-1"
208
+ assert json.loads(tool.invoke(a=2, b=3))["content"][0]["text"] == expected
209
+
210
+ toolset.close()
211
+
212
+ toolset.warm_up()
213
+ assert sorted(tool.name for tool in toolset.tools) == ["add", "subtract"]
214
+ for tool in toolset.tools:
215
+ expected = "5" if tool.name == "add" else "-1"
216
+ assert json.loads(tool.invoke(a=2, b=3))["content"][0]["text"] == expected
217
+
198
218
  async def test_toolset_serde(self, calculator_toolset):
199
219
  """Test serialization and deserialization of MCPToolset."""
200
220
  toolset = calculator_toolset
File without changes
File without changes
File without changes