mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-03 21:02:40 +02:00
Add front end logic for MCP connectors
This commit is contained in:
parent
792548b379
commit
c78ea98a68
8 changed files with 71 additions and 65 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -164,20 +164,19 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,12 +56,6 @@ export const MCPConfig: FC<MCPConfigProps> = ({ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
|
|||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
|
||||
Connect {getConnectorTypeDisplay(connectorType)}
|
||||
Connect {connectorType === "MCP_CONNECTOR" ? "MCP(s)" : getConnectorTypeDisplay(connectorType)}
|
||||
</h2>
|
||||
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
||||
Enter your connection details
|
||||
|
|
@ -139,7 +139,7 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
|
|||
Connecting...
|
||||
</>
|
||||
) : (
|
||||
<>Connect {getConnectorTypeDisplay(connectorType)}</>
|
||||
<>Connect {connectorType === "MCP_CONNECTOR" ? "MCP(s)" : getConnectorTypeDisplay(connectorType)}</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -130,6 +130,12 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
|||
(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<ActiveConnectorsTabProps> = ({
|
|||
Active
|
||||
</p>
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5">
|
||||
{mcpConnectors.length} {mcpConnectors.length === 1 ? "Server" : "Servers"}
|
||||
{totalMCPServers} {totalMCPServers === 1 ? "Server" : "Servers"}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@ export const mcpServerConfig = z.object({
|
|||
*/
|
||||
export const mcpConnectorCreate = z.object({
|
||||
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({
|
||||
name: z.string().min(1).optional(),
|
||||
server_config: mcpServerConfig.optional(),
|
||||
server_configs: z.array(mcpServerConfig).optional(),
|
||||
});
|
||||
|
||||
export const mcpConnectorRead = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
connector_type: z.literal("MCP_CONNECTOR"),
|
||||
server_config: mcpServerConfig,
|
||||
server_configs: z.array(mcpServerConfig),
|
||||
search_space_id: z.number(),
|
||||
user_id: z.string(),
|
||||
created_at: z.string(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue