From c78ea98a68514dbe721f1f8d18f7ef2a87132d67 Mon Sep 17 00:00:00 2001
From: Manoj Aggarwal
Date: Tue, 13 Jan 2026 21:10:03 -0800
Subject: [PATCH] Add front end logic for MCP connectors
---
.../app/agents/new_chat/tools/mcp_tool.py | 62 ++++++++++---------
.../routes/search_source_connectors_routes.py | 10 +--
.../app/schemas/search_source_connector.py | 13 ++--
.../components/mcp-connect-form.tsx | 27 ++++----
.../components/mcp-config.tsx | 6 --
.../views/connector-connect-view.tsx | 4 +-
.../tabs/active-connectors-tab.tsx | 8 ++-
surfsense_web/contracts/types/mcp.types.ts | 6 +-
8 files changed, 71 insertions(+), 65 deletions(-)
diff --git a/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py b/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py
index 81c7d074f..dd374cdae 100644
--- a/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py
+++ b/surfsense_backend/app/agents/new_chat/tools/mcp_tool.py
@@ -141,40 +141,46 @@ async def load_mcp_tools(
tools: list[StructuredTool] = []
for connector in result.scalars():
try:
- # Extract server config
+ # Extract server configs array
config = connector.config or {}
- server_config = config.get("server_config", {})
-
- command = server_config.get("command")
- args = server_config.get("args", [])
- env = server_config.get("env", {})
-
- if not command:
- logger.warning(f"MCP connector {connector.id} missing command, skipping")
+ server_configs = config.get("server_configs", [])
+
+ if not server_configs:
+ logger.warning(f"MCP connector {connector.id} missing server_configs, skipping")
continue
- # Create MCP client
- mcp_client = MCPClient(command, args, env)
+ # Process each server config
+ for server_config in server_configs:
+ command = server_config.get("command")
+ args = server_config.get("args", [])
+ env = server_config.get("env", {})
- # Connect and discover tools
- async with mcp_client.connect():
- tool_definitions = await mcp_client.list_tools()
+ if not command:
+ logger.warning(f"MCP connector {connector.id} server config missing command, skipping")
+ continue
- logger.info(
- f"Discovered {len(tool_definitions)} tools from MCP server "
- f"'{command}' (connector {connector.id})"
- )
+ # Create MCP client
+ mcp_client = MCPClient(command, args, env)
- # Create LangChain tools from definitions
- for tool_def in tool_definitions:
- try:
- tool = await _create_mcp_tool_from_definition(tool_def, mcp_client)
- tools.append(tool)
- except Exception as e:
- logger.exception(
- f"Failed to create tool '{tool_def.get('name')}' "
- f"from connector {connector.id}: {e!s}",
- )
+ # Connect and discover tools
+ async with mcp_client.connect():
+ tool_definitions = await mcp_client.list_tools()
+
+ logger.info(
+ f"Discovered {len(tool_definitions)} tools from MCP server "
+ f"'{command}' (connector {connector.id})"
+ )
+
+ # Create LangChain tools from definitions
+ for tool_def in tool_definitions:
+ try:
+ tool = await _create_mcp_tool_from_definition(tool_def, mcp_client)
+ tools.append(tool)
+ except Exception as e:
+ logger.exception(
+ f"Failed to create tool '{tool_def.get('name')}' "
+ f"from connector {connector.id}: {e!s}",
+ )
except Exception as e:
logger.exception(
diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py
index a7c577bba..a3d9d10ef 100644
--- a/surfsense_backend/app/routes/search_source_connectors_routes.py
+++ b/surfsense_backend/app/routes/search_source_connectors_routes.py
@@ -2018,12 +2018,12 @@ async def create_mcp_connector(
"You don't have permission to create connectors in this search space",
)
- # Create the connector with server config
+ # Create the connector with server configs array
db_connector = SearchSourceConnector(
name=connector_data.name,
connector_type=SearchSourceConnectorType.MCP_CONNECTOR,
is_indexable=False, # MCP connectors are not indexable
- config={"server_config": connector_data.server_config.model_dump()},
+ config={"server_configs": [sc.model_dump() for sc in connector_data.server_configs]},
periodic_indexing_enabled=False,
indexing_frequency_minutes=None,
search_space_id=search_space_id,
@@ -2035,7 +2035,7 @@ async def create_mcp_connector(
await session.refresh(db_connector)
logger.info(
- f"Created MCP connector {db_connector.id} for server '{connector_data.server_config.command}' "
+ f"Created MCP connector {db_connector.id} with {len(connector_data.server_configs)} server(s) "
f"for user {user.id} in search space {search_space_id}"
)
@@ -2202,9 +2202,9 @@ async def update_mcp_connector(
if connector_update.name is not None:
connector.name = connector_update.name
- if connector_update.server_config is not None:
+ if connector_update.server_configs is not None:
connector.config = {
- "server_config": connector_update.server_config.model_dump()
+ "server_configs": [sc.model_dump() for sc in connector_update.server_configs]
}
connector.updated_at = datetime.now(UTC)
diff --git a/surfsense_backend/app/schemas/search_source_connector.py b/surfsense_backend/app/schemas/search_source_connector.py
index e27cc775c..93d877261 100644
--- a/surfsense_backend/app/schemas/search_source_connector.py
+++ b/surfsense_backend/app/schemas/search_source_connector.py
@@ -93,23 +93,23 @@ class MCPConnectorCreate(BaseModel):
"""Schema for creating an MCP connector."""
name: str
- server_config: MCPServerConfig
+ server_configs: list[MCPServerConfig] # Array of MCP server configurations
class MCPConnectorUpdate(BaseModel):
"""Schema for updating an MCP connector."""
name: str | None = None
- server_config: MCPServerConfig | None = None
+ server_configs: list[MCPServerConfig] | None = None
class MCPConnectorRead(BaseModel):
- """Schema for reading an MCP connector with server config."""
+ """Schema for reading an MCP connector with server configs."""
id: int
name: str
connector_type: SearchSourceConnectorType
- server_config: MCPServerConfig
+ server_configs: list[MCPServerConfig]
search_space_id: int
user_id: uuid.UUID
created_at: datetime
@@ -121,13 +121,14 @@ class MCPConnectorRead(BaseModel):
def from_connector(cls, connector: SearchSourceConnectorRead) -> "MCPConnectorRead":
"""Convert from base SearchSourceConnectorRead."""
config = connector.config or {}
- server_config = MCPServerConfig(**config.get("server_config", {}))
+ server_configs_data = config.get("server_configs", [])
+ server_configs = [MCPServerConfig(**sc) for sc in server_configs_data]
return cls(
id=connector.id,
name=connector.name,
connector_type=connector.connector_type,
- server_config=server_config,
+ server_configs=server_configs,
search_space_id=connector.search_space_id,
user_id=connector.user_id,
created_at=connector.created_at,
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx
index 3ef43b3db..34a07c6f5 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx
+++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/mcp-connect-form.tsx
@@ -164,20 +164,19 @@ export const MCPConnectForm: FC = ({ onSubmit, isSubmitting })
isSubmittingRef.current = true;
try {
- // Submit all servers
- for (const config of configs) {
- await onSubmit({
- name: config.name,
- connector_type: EnumConnectorName.MCP_CONNECTOR,
- config: { server_config: config },
- is_indexable: false,
- is_active: true,
- last_indexed_at: null,
- periodic_indexing_enabled: false,
- indexing_frequency_minutes: null,
- next_scheduled_at: null,
- });
- }
+ // Submit all servers as a single connector with server_configs array
+ // This creates one connector instead of N connectors (one toast instead of N toasts)
+ await onSubmit({
+ name: configs.length === 1 ? configs[0].name : "MCPs",
+ connector_type: EnumConnectorName.MCP_CONNECTOR,
+ config: { server_configs: configs },
+ is_indexable: false,
+ is_active: true,
+ last_indexed_at: null,
+ periodic_indexing_enabled: false,
+ indexing_frequency_minutes: null,
+ next_scheduled_at: null,
+ });
} finally {
isSubmittingRef.current = false;
}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx
index 771107c93..d010967d2 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx
+++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/mcp-config.tsx
@@ -56,12 +56,6 @@ export const MCPConfig: FC = ({ connector, onConfigChange, onNam
const serverConfigs = mcpConn.config?.server_configs as MCPServerConfig[] | undefined;
if (serverConfigs && Array.isArray(serverConfigs)) {
allServerConfigs.push(...serverConfigs);
- } else {
- // Fallback to single server_config
- const serverConfig = mcpConn.config?.server_config as MCPServerConfig | undefined;
- if (serverConfig) {
- allServerConfigs.push(serverConfig);
- }
}
}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx
index 5d198436d..ff69e3947 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx
+++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx
@@ -99,7 +99,7 @@ export const ConnectorConnectView: FC = ({
- Connect {getConnectorTypeDisplay(connectorType)}
+ Connect {connectorType === "MCP_CONNECTOR" ? "MCP(s)" : getConnectorTypeDisplay(connectorType)}
Enter your connection details
@@ -139,7 +139,7 @@ export const ConnectorConnectView: FC = ({
Connecting...
>
) : (
- <>Connect {getConnectorTypeDisplay(connectorType)}>
+ <>Connect {connectorType === "MCP_CONNECTOR" ? "MCP(s)" : getConnectorTypeDisplay(connectorType)}>
)}
diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx
index d05a4c8dd..1b1c3ea4d 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx
+++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/active-connectors-tab.tsx
@@ -130,6 +130,12 @@ export const ActiveConnectorsTab: FC = ({
(c) => !oauthConnectorTypes.has(c.connector_type) && c.connector_type !== "MCP_CONNECTOR"
);
+ // Calculate total number of MCP servers across all MCP connectors
+ const totalMCPServers = mcpConnectors.reduce((total, connector) => {
+ const serverConfigs = connector.config?.server_configs;
+ return total + (Array.isArray(serverConfigs) ? serverConfigs.length : 0);
+ }, 0);
+
// Group OAuth connectors by type
const oauthConnectorsByType = oauthConnectors.reduce(
(acc, connector) => {
@@ -305,7 +311,7 @@ export const ActiveConnectorsTab: FC = ({
Active
- {mcpConnectors.length} {mcpConnectors.length === 1 ? "Server" : "Servers"}
+ {totalMCPServers} {totalMCPServers === 1 ? "Server" : "Servers"}