diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/mcp_tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/mcp_tools/index.py index 76363937d..a8a2ffcaa 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/mcp_tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/mcp_tools/index.py @@ -21,7 +21,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.agents.multi_agent_chat.constants import ( CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS, ) -from app.agents.shared.tools.mcp_tool import load_mcp_tools +from app.agents.shared.tools.mcp.tool import load_mcp_tools from app.db import SearchSourceConnector logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/shared/tools/mcp/__init__.py b/surfsense_backend/app/agents/shared/tools/mcp/__init__.py new file mode 100644 index 000000000..07a5b02de --- /dev/null +++ b/surfsense_backend/app/agents/shared/tools/mcp/__init__.py @@ -0,0 +1,7 @@ +"""MCP (Model Context Protocol) integration: client, tool loading, and cache. + +Split by responsibility: +- ``client``: the low-level :class:`MCPClient` connection wrapper. +- ``tool``: discovery + LangChain tool construction and cache invalidation. +- ``cache``: the connector tool-cache refresh helpers. +""" diff --git a/surfsense_backend/app/agents/shared/tools/mcp_tools_cache.py b/surfsense_backend/app/agents/shared/tools/mcp/cache.py similarity index 97% rename from surfsense_backend/app/agents/shared/tools/mcp_tools_cache.py rename to surfsense_backend/app/agents/shared/tools/mcp/cache.py index bd89856ae..8a7365f2c 100644 --- a/surfsense_backend/app/agents/shared/tools/mcp_tools_cache.py +++ b/surfsense_backend/app/agents/shared/tools/mcp/cache.py @@ -112,7 +112,7 @@ def refresh_mcp_tools_cache_for_connector( when an event loop is available. Neither path raises. """ try: - from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache + from app.agents.shared.tools.mcp.tool import invalidate_mcp_tools_cache invalidate_mcp_tools_cache(search_space_id) except Exception: @@ -133,7 +133,7 @@ def refresh_mcp_tools_cache_for_connector( async def _run_connector_prefetch(connector_id: int) -> None: - from app.agents.shared.tools.mcp_tool import discover_single_mcp_connector + from app.agents.shared.tools.mcp.tool import discover_single_mcp_connector try: await discover_single_mcp_connector(connector_id) diff --git a/surfsense_backend/app/agents/shared/tools/mcp_client.py b/surfsense_backend/app/agents/shared/tools/mcp/client.py similarity index 100% rename from surfsense_backend/app/agents/shared/tools/mcp_client.py rename to surfsense_backend/app/agents/shared/tools/mcp/client.py diff --git a/surfsense_backend/app/agents/shared/tools/mcp_tool.py b/surfsense_backend/app/agents/shared/tools/mcp/tool.py similarity index 99% rename from surfsense_backend/app/agents/shared/tools/mcp_tool.py rename to surfsense_backend/app/agents/shared/tools/mcp/tool.py index 8e688a71b..81367f2fd 100644 --- a/surfsense_backend/app/agents/shared/tools/mcp_tool.py +++ b/surfsense_backend/app/agents/shared/tools/mcp/tool.py @@ -35,8 +35,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.agents.shared.middleware.dedup_tool_calls import dedup_key_full_args from app.agents.shared.tools.hitl import request_approval -from app.agents.shared.tools.mcp_client import MCPClient -from app.agents.shared.tools.mcp_tools_cache import ( +from app.agents.shared.tools.mcp.client import MCPClient +from app.agents.shared.tools.mcp.cache import ( CachedMCPTools, read_cached_tools, write_cached_tools, diff --git a/surfsense_backend/app/routes/mcp_oauth_route.py b/surfsense_backend/app/routes/mcp_oauth_route.py index 89049c1ca..5a42d86bd 100644 --- a/surfsense_backend/app/routes/mcp_oauth_route.py +++ b/surfsense_backend/app/routes/mcp_oauth_route.py @@ -665,7 +665,7 @@ def _refresh_mcp_cache(connector_id: int, space_id: int) -> None: isolated from the OAuth response flow. """ try: - from app.agents.shared.tools.mcp_tools_cache import ( + from app.agents.shared.tools.mcp.cache import ( refresh_mcp_tools_cache_for_connector, ) diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index 32ecac6fa..04407edf3 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -675,7 +675,7 @@ async def delete_search_source_connector( await session.commit() if is_mcp: - from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache + from app.agents.shared.tools.mcp.tool import invalidate_mcp_tools_cache invalidate_mcp_tools_cache(search_space_id) @@ -2687,7 +2687,7 @@ async def create_mcp_connector( f"for user {user.id} in search space {search_space_id}" ) - from app.agents.shared.tools.mcp_tools_cache import ( + from app.agents.shared.tools.mcp.cache import ( refresh_mcp_tools_cache_for_connector, ) @@ -2867,7 +2867,7 @@ async def update_mcp_connector( logger.info(f"Updated MCP connector {connector_id}") - from app.agents.shared.tools.mcp_tools_cache import ( + from app.agents.shared.tools.mcp.cache import ( refresh_mcp_tools_cache_for_connector, ) @@ -2927,7 +2927,7 @@ async def delete_mcp_connector( await session.delete(connector) await session.commit() - from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache + from app.agents.shared.tools.mcp.tool import invalidate_mcp_tools_cache invalidate_mcp_tools_cache(search_space_id) @@ -2966,7 +2966,7 @@ async def test_mcp_server_connection( Connection status and list of available tools """ try: - from app.agents.shared.tools.mcp_client import ( + from app.agents.shared.tools.mcp.client import ( test_mcp_connection, test_mcp_http_connection, ) @@ -3157,7 +3157,7 @@ async def trust_mcp_tool( connectors (``LINEAR_CONNECTOR``, ``JIRA_CONNECTOR``, ...) — the storage primitive is the same JSON list under ``config.trusted_tools``. """ - from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache + from app.agents.shared.tools.mcp.tool import invalidate_mcp_tools_cache from app.services.user_tool_allowlist import add_user_trust try: @@ -3197,7 +3197,7 @@ async def untrust_mcp_tool( The tool will require HITL approval again on subsequent calls. """ - from app.agents.shared.tools.mcp_tool import invalidate_mcp_tools_cache + from app.agents.shared.tools.mcp.tool import invalidate_mcp_tools_cache from app.services.user_tool_allowlist import remove_user_trust try: diff --git a/surfsense_backend/tests/e2e/fakes/mcp_runtime.py b/surfsense_backend/tests/e2e/fakes/mcp_runtime.py index ffd070816..afaf7685e 100644 --- a/surfsense_backend/tests/e2e/fakes/mcp_runtime.py +++ b/surfsense_backend/tests/e2e/fakes/mcp_runtime.py @@ -137,10 +137,10 @@ def install(active_patches: list[Any]) -> None: """Patch production MCP streamable-HTTP boundaries exactly once.""" targets = [ ( - "app.agents.shared.tools.mcp_tool.streamablehttp_client", + "app.agents.shared.tools.mcp.tool.streamablehttp_client", _fake_streamablehttp_client, ), - ("app.agents.shared.tools.mcp_tool.ClientSession", _FakeClientSession), + ("app.agents.shared.tools.mcp.tool.ClientSession", _FakeClientSession), ] for target, replacement in targets: p = patch(target, replacement) diff --git a/surfsense_backend/tests/unit/agents/new_chat/tools/test_mcp_tools_cache.py b/surfsense_backend/tests/unit/agents/new_chat/tools/test_mcp_tools_cache.py index 90337dd7b..6958ef795 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/tools/test_mcp_tools_cache.py +++ b/surfsense_backend/tests/unit/agents/new_chat/tools/test_mcp_tools_cache.py @@ -7,7 +7,7 @@ from types import SimpleNamespace import pytest -from app.agents.shared.tools.mcp_tools_cache import ( +from app.agents.shared.tools.mcp.cache import ( CachedMCPToolDef, CachedMCPTools, read_cached_tools,