diff --git a/langchain_mcp_adapters/sessions.py b/langchain_mcp_adapters/sessions.py index a8af8a5..60172bc 100644 --- a/langchain_mcp_adapters/sessions.py +++ b/langchain_mcp_adapters/sessions.py @@ -140,10 +140,10 @@ class StreamableHttpConnection(TypedDict): headers: NotRequired[dict[str, Any] | None] """HTTP headers to send to the endpoint.""" - timeout: NotRequired[timedelta] + timeout: NotRequired[float | timedelta] """HTTP timeout.""" - sse_read_timeout: NotRequired[timedelta] + sse_read_timeout: NotRequired[float | timedelta] """How long (in seconds) the client will wait for a new event before disconnecting. All other HTTP operations are controlled by `timeout`.""" diff --git a/pyproject.toml b/pyproject.toml index b77cf8d..fd96300 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "langchain-core>=0.3.36,<0.4", - "mcp>=1.9.2", + "mcp==1.9.2", "typing-extensions>=4.14.0", ] diff --git a/tests/test_tools.py b/tests/test_tools.py index 42ab8b4..9fe2fa9 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,10 +1,12 @@ from typing import Annotated from unittest.mock import AsyncMock, MagicMock +import httpx import pytest from langchain_core.callbacks import CallbackManagerForToolRun from langchain_core.messages import ToolMessage from langchain_core.tools import BaseTool, InjectedToolArg, ToolException, tool +from mcp.server import FastMCP from mcp.types import ( CallToolResult, EmbeddedResource, @@ -368,10 +370,8 @@ def get_status() -> str: # Tests for httpx_client_factory functionality -@pytest.mark.asyncio async def test_load_mcp_tools_with_custom_httpx_client_factory(socket_enabled) -> None: """Test load mcp tools with custom httpx client factory.""" - import httpx # Custom httpx client factory def custom_httpx_client_factory( @@ -423,7 +423,17 @@ def get_info() -> str: return server -@pytest.mark.asyncio +def _create_failing_server() -> FastMCP: + server = FastMCP(port=8184) + + @server.tool() + def failing_tool(message: str) -> str: + """A tool that always fails with an exception""" + raise ValueError(f"Tool failed: {message}") + + return server + + async def test_load_mcp_tools_with_custom_httpx_client_factory_sse( socket_enabled, ) -> None: @@ -468,3 +478,28 @@ def custom_httpx_client_factory( # Expected to fail since server doesn't have SSE endpoint, # but the important thing is that httpx_client_factory was passed correctly pass + + +async def test_mcp_tool_exception_handling(socket_enabled) -> None: + """Test that exceptions from MCP tools are properly handled on the client side.""" + with run_streamable_http(_create_failing_server, 8184): + # Initialize client + client = MultiServerMCPClient( + { + "failing": { + "url": "http://localhost:8184/mcp/", + "transport": "streamable_http", + "timeout": 5, + "sse_read_timeout": 2, + } + }, + ) + + # Get the failing tool + tools = await client.get_tools() + assert len(tools) == 1 + tool = tools[0] + assert tool.name == "failing_tool" + await tool.ainvoke( + {"args": {"message": "test error"}, "id": "1", "type": "tool_call"} + ) diff --git a/uv.lock b/uv.lock index b1f1e0f..c1123fe 100644 --- a/uv.lock +++ b/uv.lock @@ -318,7 +318,7 @@ test = [ [package.metadata] requires-dist = [ { name = "langchain-core", specifier = ">=0.3.36,<0.4" }, - { name = "mcp", specifier = ">=1.9.2" }, + { name = "mcp", specifier = "==1.9.2" }, { name = "typing-extensions", specifier = ">=4.14.0" }, ] @@ -354,7 +354,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.9.3" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -367,9 +367,9 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/df/8fefc0c6c7a5c66914763e3ff3893f9a03435628f6625d5e3b0dc45d73db/mcp-1.9.3.tar.gz", hash = "sha256:587ba38448e81885e5d1b84055cfcc0ca56d35cd0c58f50941cab01109405388", size = 333045, upload-time = "2025-06-05T15:48:25.681Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/03/77c49cce3ace96e6787af624611b627b2828f0dca0f8df6f330a10eea51e/mcp-1.9.2.tar.gz", hash = "sha256:3c7651c053d635fd235990a12e84509fe32780cd359a5bbef352e20d4d963c05", size = 333066, upload-time = "2025-05-29T14:42:17.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/45/823ad05504bea55cb0feb7470387f151252127ad5c72f8882e8fe6cf5c0e/mcp-1.9.3-py3-none-any.whl", hash = "sha256:69b0136d1ac9927402ed4cf221d4b8ff875e7132b0b06edd446448766f34f9b9", size = 131063, upload-time = "2025-06-05T15:48:24.171Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a6/8f5ee9da9f67c0fd8933f63d6105f02eabdac8a8c0926728368ffbb6744d/mcp-1.9.2-py3-none-any.whl", hash = "sha256:bc29f7fd67d157fef378f89a4210384f5fecf1168d0feb12d22929818723f978", size = 131083, upload-time = "2025-05-29T14:42:16.211Z" }, ] [[package]]