From c51aca6ccccd162a2189882e6a21916610db79a4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 4 Jun 2026 20:35:38 +0200 Subject: [PATCH] refactor(agents): group MCP tools into shared/tools/mcp/ subpackage The three MCP siblings (mcp_client/mcp_tool/mcp_tools_cache) served one objective but sat loose at the top of shared/tools. Grouped them into an mcp/ package and dropped the redundant prefix: client.py, tool.py, cache.py. Updated all importers (routes, mcp_tools subagent, e2e fake patch targets, unit test) to the new paths. --- .../multi_agent_chat/subagents/mcp_tools/index.py | 2 +- .../app/agents/shared/tools/mcp/__init__.py | 7 +++++++ .../tools/{mcp_tools_cache.py => mcp/cache.py} | 4 ++-- .../shared/tools/{mcp_client.py => mcp/client.py} | 0 .../shared/tools/{mcp_tool.py => mcp/tool.py} | 4 ++-- surfsense_backend/app/routes/mcp_oauth_route.py | 2 +- .../app/routes/search_source_connectors_routes.py | 14 +++++++------- surfsense_backend/tests/e2e/fakes/mcp_runtime.py | 4 ++-- .../agents/new_chat/tools/test_mcp_tools_cache.py | 2 +- 9 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 surfsense_backend/app/agents/shared/tools/mcp/__init__.py rename surfsense_backend/app/agents/shared/tools/{mcp_tools_cache.py => mcp/cache.py} (97%) rename surfsense_backend/app/agents/shared/tools/{mcp_client.py => mcp/client.py} (100%) rename surfsense_backend/app/agents/shared/tools/{mcp_tool.py => mcp/tool.py} (99%) 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,