mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-12 09:12: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] = []
|
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", [])
|
||||||
|
|
||||||
command = server_config.get("command")
|
if not server_configs:
|
||||||
args = server_config.get("args", [])
|
logger.warning(f"MCP connector {connector.id} missing server_configs, skipping")
|
||||||
env = server_config.get("env", {})
|
|
||||||
|
|
||||||
if not command:
|
|
||||||
logger.warning(f"MCP connector {connector.id} missing command, skipping")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Create MCP client
|
# Process each server config
|
||||||
mcp_client = MCPClient(command, args, env)
|
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
|
if not command:
|
||||||
async with mcp_client.connect():
|
logger.warning(f"MCP connector {connector.id} server config missing command, skipping")
|
||||||
tool_definitions = await mcp_client.list_tools()
|
continue
|
||||||
|
|
||||||
logger.info(
|
# Create MCP client
|
||||||
f"Discovered {len(tool_definitions)} tools from MCP server "
|
mcp_client = MCPClient(command, args, env)
|
||||||
f"'{command}' (connector {connector.id})"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create LangChain tools from definitions
|
# Connect and discover tools
|
||||||
for tool_def in tool_definitions:
|
async with mcp_client.connect():
|
||||||
try:
|
tool_definitions = await mcp_client.list_tools()
|
||||||
tool = await _create_mcp_tool_from_definition(tool_def, mcp_client)
|
|
||||||
tools.append(tool)
|
logger.info(
|
||||||
except Exception as e:
|
f"Discovered {len(tool_definitions)} tools from MCP server "
|
||||||
logger.exception(
|
f"'{command}' (connector {connector.id})"
|
||||||
f"Failed to create tool '{tool_def.get('name')}' "
|
)
|
||||||
f"from connector {connector.id}: {e!s}",
|
|
||||||
)
|
# 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:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -164,20 +164,19 @@ 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,
|
||||||
periodic_indexing_enabled: false,
|
periodic_indexing_enabled: false,
|
||||||
indexing_frequency_minutes: null,
|
indexing_frequency_minutes: null,
|
||||||
next_scheduled_at: null,
|
next_scheduled_at: null,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
isSubmittingRef.current = false;
|
isSubmittingRef.current = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue