fix(mcp): show Installed state for catalog entries already installed (#1405)

This commit is contained in:
m57
2026-09-30 13:15:30 +03:00
committed by GitHub
parent 81b5e6d84b
commit 6810ef02fe
6 changed files with 92 additions and 22 deletions
@@ -585,6 +585,7 @@ class McpServersViewModel(
)
}
loadServers(forceRefresh = true)
loadCatalog()
}
is NetworkResult.Failure -> {
@@ -9,11 +9,17 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
@@ -47,6 +53,7 @@ fun CatalogView(
filteredCatalog: List<McpCatalogEntry>,
modifier: Modifier = Modifier,
) {
val installedNames = remember(state.servers) { state.servers.map { it.name }.toSet() }
Column(modifier = modifier.fillMaxSize()) {
SearchBar(
query = state.catalogQuery,
@@ -88,6 +95,7 @@ fun CatalogView(
state = state,
viewModel = viewModel,
spacing = spacing,
isInstalled = entry.installed || entry.name in installedNames,
)
}
}
@@ -103,6 +111,7 @@ fun CatalogEntryCard(
viewModel: McpServersViewModel,
spacing: Spacing,
modifier: Modifier = Modifier,
isInstalled: Boolean = false,
) {
var showInstallForm by remember { mutableStateOf(false) }
val isInstalling = state.installingCatalogEntry == entry.name
@@ -131,23 +140,31 @@ fun CatalogEntryCard(
)
}
}
Button(
onClick = {
if (entry.env?.isNotEmpty() == true) {
showInstallForm = !showInstallForm
} else {
viewModel.installCatalogEntry(entry)
}
},
enabled = !isInstalling,
) {
Text(
if (isInstalling) {
stringResource(R.string.mcp_servers_catalog_installing)
} else {
stringResource(R.string.mcp_servers_catalog_install)
if (isInstalled) {
FilledTonalButton(onClick = {}, enabled = false) {
Icon(Icons.Filled.Check, contentDescription = null, modifier = Modifier.size(16.dp))
Spacer(modifier = Modifier.width(spacing.xs))
Text(stringResource(R.string.mcp_servers_catalog_installed))
}
} else {
Button(
onClick = {
if (entry.env?.isNotEmpty() == true) {
showInstallForm = !showInstallForm
} else {
viewModel.installCatalogEntry(entry)
}
},
)
enabled = !isInstalling,
) {
Text(
if (isInstalling) {
stringResource(R.string.mcp_servers_catalog_installing)
} else {
stringResource(R.string.mcp_servers_catalog_install)
},
)
}
}
}
@@ -161,7 +178,7 @@ fun CatalogEntryCard(
}
// Install form with env vars
AnimatedVisibility(visible = showInstallForm) {
AnimatedVisibility(visible = showInstallForm && !isInstalled) {
Column(modifier = Modifier.padding(top = spacing.sm)) {
entry.env?.let { envVars ->
if (envVars.isNotEmpty()) {
@@ -151,7 +151,7 @@ fun ServerCard(
// Tool count & schema token overhead badge (issue #1029)
val toolInfos = testResult?.tools
val toolCount = toolInfos?.size ?: server.tools?.size
val toolCount = toolInfos?.size ?: server.toolCount
if (toolCount != null && toolCount > 0) {
val tokenEst = toolInfos?.let { McpTokenEstimator.estimateTokens(it) }
val label = McpTokenEstimator.formatTokenOverhead(toolCount, tokenEst)