Add front end logic for MCP connectors

This commit is contained in:
Manoj Aggarwal 2026-01-13 21:10:03 -08:00
parent 792548b379
commit c78ea98a68
8 changed files with 71 additions and 65 deletions

View file

@ -141,16 +141,22 @@ async def load_mcp_tools(
tools: list[StructuredTool] = [] tools: list[StructuredTool] = []
for connector in result.scalars(): for connector in result.scalars():
try: try:
# Extract server config # Extract server configs array
config = connector.config or {} config = connector.config or {}
server_config = config.get("server_config", {}) server_configs = config.get("server_configs", [])
if not server_configs:
logger.warning(f"MCP connector {connector.id} missing server_configs, skipping")
continue
# Process each server config
for server_config in server_configs:
command = server_config.get("command") command = server_config.get("command")
args = server_config.get("args", []) args = server_config.get("args", [])
env = server_config.get("env", {}) env = server_config.get("env", {})
if not command: if not command:
logger.warning(f"MCP connector {connector.id} missing command, skipping") logger.warning(f"MCP connector {connector.id} server config missing command, skipping")
continue continue
# Create MCP client # Create MCP client

View file

@ -2018,12 +2018,12 @@ async def create_mcp_connector(
"You don't have permission to create connectors in this search space", "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( db_connector = SearchSourceConnector(
name=connector_data.name, name=connector_data.name,
connector_type=SearchSourceConnectorType.MCP_CONNECTOR, connector_type=SearchSourceConnectorType.MCP_CONNECTOR,
is_indexable=False, # MCP connectors are not indexable 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, periodic_indexing_enabled=False,
indexing_frequency_minutes=None, indexing_frequency_minutes=None,
search_space_id=search_space_id, search_space_id=search_space_id,
@ -2035,7 +2035,7 @@ async def create_mcp_connector(
await session.refresh(db_connector) await session.refresh(db_connector)
logger.info( 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}" 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: if connector_update.name is not None:
connector.name = connector_update.name connector.name = connector_update.name
if connector_update.server_config is not None: if connector_update.server_configs is not None:
connector.config = { 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) connector.updated_at = datetime.now(UTC)

View file

@ -93,23 +93,23 @@ class MCPConnectorCreate(BaseModel):
"""Schema for creating an MCP connector.""" """Schema for creating an MCP connector."""
name: str name: str
server_config: MCPServerConfig server_configs: list[MCPServerConfig] # Array of MCP server configurations
class MCPConnectorUpdate(BaseModel): class MCPConnectorUpdate(BaseModel):
"""Schema for updating an MCP connector.""" """Schema for updating an MCP connector."""
name: str | None = None name: str | None = None
server_config: MCPServerConfig | None = None server_configs: list[MCPServerConfig] | None = None
class MCPConnectorRead(BaseModel): class MCPConnectorRead(BaseModel):
"""Schema for reading an MCP connector with server config.""" """Schema for reading an MCP connector with server configs."""
id: int id: int
name: str name: str
connector_type: SearchSourceConnectorType connector_type: SearchSourceConnectorType
server_config: MCPServerConfig server_configs: list[MCPServerConfig]
search_space_id: int search_space_id: int
user_id: uuid.UUID user_id: uuid.UUID
created_at: datetime created_at: datetime
@ -121,13 +121,14 @@ class MCPConnectorRead(BaseModel):
def from_connector(cls, connector: SearchSourceConnectorRead) -> "MCPConnectorRead": def from_connector(cls, connector: SearchSourceConnectorRead) -> "MCPConnectorRead":
"""Convert from base SearchSourceConnectorRead.""" """Convert from base SearchSourceConnectorRead."""
config = connector.config or {} 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( return cls(
id=connector.id, id=connector.id,
name=connector.name, name=connector.name,
connector_type=connector.connector_type, connector_type=connector.connector_type,
server_config=server_config, server_configs=server_configs,
search_space_id=connector.search_space_id, search_space_id=connector.search_space_id,
user_id=connector.user_id, user_id=connector.user_id,
created_at=connector.created_at, created_at=connector.created_at,

View file

@ -164,12 +164,12 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
isSubmittingRef.current = true; isSubmittingRef.current = true;
try { try {
// Submit all servers // Submit all servers as a single connector with server_configs array
for (const config of configs) { // This creates one connector instead of N connectors (one toast instead of N toasts)
await onSubmit({ await onSubmit({
name: config.name, name: configs.length === 1 ? configs[0].name : "MCPs",
connector_type: EnumConnectorName.MCP_CONNECTOR, connector_type: EnumConnectorName.MCP_CONNECTOR,
config: { server_config: config }, config: { server_configs: configs },
is_indexable: false, is_indexable: false,
is_active: true, is_active: true,
last_indexed_at: null, last_indexed_at: null,
@ -177,7 +177,6 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}); });
}
} finally { } finally {
isSubmittingRef.current = false; isSubmittingRef.current = false;
} }

View file

@ -56,12 +56,6 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
const serverConfigs = mcpConn.config?.server_configs as MCPServerConfig[] | undefined; const serverConfigs = mcpConn.config?.server_configs as MCPServerConfig[] | undefined;
if (serverConfigs && Array.isArray(serverConfigs)) { if (serverConfigs && Array.isArray(serverConfigs)) {
allServerConfigs.push(...serverConfigs); allServerConfigs.push(...serverConfigs);
} else {
// Fallback to single server_config
const serverConfig = mcpConn.config?.server_config as MCPServerConfig | undefined;
if (serverConfig) {
allServerConfigs.push(serverConfig);
}
} }
} }

View file

@ -99,7 +99,7 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
</div> </div>
<div> <div>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight"> <h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
Connect {getConnectorTypeDisplay(connectorType)} Connect {connectorType === "MCP_CONNECTOR" ? "MCP(s)" : getConnectorTypeDisplay(connectorType)}
</h2> </h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1"> <p className="text-xs sm:text-base text-muted-foreground mt-1">
Enter your connection details Enter your connection details
@ -139,7 +139,7 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
Connecting... Connecting...
</> </>
) : ( ) : (
<>Connect {getConnectorTypeDisplay(connectorType)}</> <>Connect {connectorType === "MCP_CONNECTOR" ? "MCP(s)" : getConnectorTypeDisplay(connectorType)}</>
)} )}
</Button> </Button>
</div> </div>

View file

@ -130,6 +130,12 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
(c) => !oauthConnectorTypes.has(c.connector_type) && c.connector_type !== "MCP_CONNECTOR" (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 // Group OAuth connectors by type
const oauthConnectorsByType = oauthConnectors.reduce( const oauthConnectorsByType = oauthConnectors.reduce(
(acc, connector) => { (acc, connector) => {
@ -305,7 +311,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
Active Active
</p> </p>
<p className="text-[10px] text-muted-foreground mt-0.5"> <p className="text-[10px] text-muted-foreground mt-0.5">
{mcpConnectors.length} {mcpConnectors.length === 1 ? "Server" : "Servers"} {totalMCPServers} {totalMCPServers === 1 ? "Server" : "Servers"}
</p> </p>
</div> </div>
<Button <Button

View file

@ -15,19 +15,19 @@ export const mcpServerConfig = z.object({
*/ */
export const mcpConnectorCreate = z.object({ export const mcpConnectorCreate = z.object({
name: z.string().min(1, "Connector name is required"), name: z.string().min(1, "Connector name is required"),
server_config: mcpServerConfig, server_configs: z.array(mcpServerConfig).min(1, "At least one server configuration is required"),
}); });
export const mcpConnectorUpdate = z.object({ export const mcpConnectorUpdate = z.object({
name: z.string().min(1).optional(), name: z.string().min(1).optional(),
server_config: mcpServerConfig.optional(), server_configs: z.array(mcpServerConfig).optional(),
}); });
export const mcpConnectorRead = z.object({ export const mcpConnectorRead = z.object({
id: z.number(), id: z.number(),
name: z.string(), name: z.string(),
connector_type: z.literal("MCP_CONNECTOR"), connector_type: z.literal("MCP_CONNECTOR"),
server_config: mcpServerConfig, server_configs: z.array(mcpServerConfig),
search_space_id: z.number(), search_space_id: z.number(),
user_id: z.string(), user_id: z.string(),
created_at: z.string(), created_at: z.string(),