RIDER-140018 [rider-mcp]: apply MCP product filters in tools settings

The settings configurable needs to show the tools that are actually available for the product while still letting users edit their own MCP tool filters.

Apply MCP tool filter providers when rebuilding settings groups, excluding only the settings-backed providers that would otherwise filter the configurable by its own persisted state. This keeps Rider-specific replacements such as rename_refactoring hidden from the settings list while preserving user control over the exposed tools. Settings-backed providers are marked with the UserConfigurableMcpToolFilterProvider marker interface instead of hard-coding their classes in the configurable.

RIDER-140018 fix


IJ-MR-214874

GitOrigin-RevId: 4214c5aa340cd19d09d87830ebe76057e43e65f7
This commit is contained in:
Vladislav Annenkov
2026-07-24 16:57:13 +00:00
committed by intellij-monorepo-bot
parent 3bed40366f
commit 09a095f714
5 changed files with 86 additions and 6 deletions
@@ -9,7 +9,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class DisallowListBasedMcpToolFilterProvider : McpToolFilterProvider {
internal class DisallowListBasedMcpToolFilterProvider : UserConfigurableMcpToolFilterProvider {
override fun applyFilters(context: McpToolFilterContext, clientInfo: Implementation?, sessionOptions: McpServerService.McpSessionOptions?, invocationMode: McpToolInvocationMode) {
val settings = McpToolDisallowListSettings.getInstance()
@@ -10,7 +10,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class SettingsBasedMcpToolFilterProvider : McpToolFilterProvider {
internal class SettingsBasedMcpToolFilterProvider : UserConfigurableMcpToolFilterProvider {
override fun applyFilters(context: McpToolFilterContext, clientInfo: Implementation?, sessionOptions: McpServerService.McpSessionOptions?, invocationMode: McpToolInvocationMode) {
val settings = McpToolFilterSettings.getInstance()
applyMaskFilter(context, settings.toolsFilter)
@@ -0,0 +1,13 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.mcpserver.impl
import com.intellij.mcpserver.McpToolFilterProvider
/**
* Marker for [McpToolFilterProvider]s whose filtering reflects the user-editable settings managed by the MCP tools
* settings UI ([com.intellij.mcpserver.settings.McpToolFilterConfigurable]).
*
* The configurable excludes such providers when building its tool list, so that the list shows every tool available
* in the product instead of being filtered by the configurable's own persisted state.
*/
internal interface UserConfigurableMcpToolFilterProvider : McpToolFilterProvider
@@ -8,6 +8,7 @@ import com.intellij.mcpserver.McpTool
import com.intellij.mcpserver.McpToolCategory
import com.intellij.mcpserver.McpToolsMarkdownExporter
import com.intellij.mcpserver.impl.McpServerService
import com.intellij.mcpserver.impl.UserConfigurableMcpToolFilterProvider
import com.intellij.mcpserver.toolsets.general.UniversalToolset
import com.intellij.mcpserver.settings.McpToolDisallowListSettings.ToolState
import com.intellij.openapi.fileChooser.FileChooserFactory
@@ -464,8 +465,7 @@ class McpToolFilterConfigurable : SearchableConfigurable {
emptyStateLabel = null
allTools = McpServerService.getInstance().getMcpToolsFiltered(
useFiltersFromEP = false,
excludeProviders = emptySet(),
excludeProviders = setOf(UserConfigurableMcpToolFilterProvider::class.java),
)
initialToolStates = normalizeToolStateKeys(initialToolStates)
val normalizedToolStates = normalizeToolStateKeys(allToolStates)
@@ -1,17 +1,26 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.mcpserver.settings
import com.intellij.mcpserver.McpSessionInvocationMode
import com.intellij.mcpserver.McpTool
import com.intellij.mcpserver.McpToolCallResult
import com.intellij.mcpserver.McpToolCategory
import com.intellij.mcpserver.McpToolDescriptor
import com.intellij.mcpserver.McpToolFilterProvider
import com.intellij.mcpserver.McpToolInvocationMode
import com.intellij.mcpserver.McpToolSchema
import com.intellij.mcpserver.McpSessionInvocationMode
import com.intellij.mcpserver.impl.McpServerService
import com.intellij.mcpserver.settings.McpToolDisallowListSettings.ToolState
import com.intellij.mcpserver.toolsets.general.UniversalToolset
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.ExtensionTestUtil
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.ui.components.JBCheckBox
import com.intellij.util.ui.ThreeStateCheckBox
import com.intellij.testFramework.junit5.TestApplication
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import org.assertj.core.api.Assertions.assertThat
@@ -146,6 +155,35 @@ class McpToolFilterConfigurableTest {
}
}
@Test
fun `configurable applies product filter providers but ignores user settings filters`() {
val disposable = Disposer.newDisposable()
val filteredTool = McpServerService.getInstance()
.getMcpToolsFiltered(useFiltersFromEP = false, excludeProviders = emptySet())
.first { it.descriptor.name != UniversalToolset::execute_tool.name }
val filteredToolName = filteredTool.descriptor.fullyQualifiedName
val configurable = McpToolFilterConfigurable()
try {
McpToolFilterSettings.getInstance().toolsFilter = "-*"
ExtensionTestUtil.addExtensions(
McpToolFilterProvider.EP,
listOf(disableToolFilterProvider(filteredToolName)),
disposable,
)
configurable.createComponent()
val configurableToolNames = configurableTools(configurable).map { it.descriptor.fullyQualifiedName }
assertThat(configurableToolNames)
.doesNotContain(filteredToolName)
.isNotEmpty()
}
finally {
configurable.disposeUIResources()
Disposer.dispose(disposable)
}
}
@Test
fun `category on demand state ignores disabled tools`() {
val configurable = McpToolFilterConfigurable()
@@ -230,6 +268,13 @@ class McpToolFilterConfigurableTest {
return method.invoke(configurable, tools) as ThreeStateCheckBox.State
}
@Suppress("UNCHECKED_CAST")
private fun configurableTools(configurable: McpToolFilterConfigurable): List<McpTool> {
val field = McpToolFilterConfigurable::class.java.getDeclaredField("allTools")
field.isAccessible = true
return field.get(configurable) as List<McpTool>
}
private fun testTool(name: String, fullyQualifiedName: String): McpTool {
return object : McpTool {
override val descriptor: McpToolDescriptor = McpToolDescriptor(
@@ -249,4 +294,26 @@ class McpToolFilterConfigurableTest {
}
}
}
private fun disableToolFilterProvider(toolName: String): McpToolFilterProvider {
return object : McpToolFilterProvider {
override fun applyFilters(
context: McpToolFilterProvider.McpToolFilterContext,
clientInfo: Implementation?,
sessionOptions: McpServerService.McpSessionOptions?,
invocationMode: McpToolInvocationMode,
) {
context.updateState(enabled = false) { tool -> tool.descriptor.fullyQualifiedName == toolName }
}
override fun getUpdates(
clientInfo: Implementation?,
scope: CoroutineScope,
sessionOptions: McpServerService.McpSessionOptions?,
invocationMode: McpToolInvocationMode,
): Flow<Unit> {
return emptyFlow()
}
}
}
}