Summary
getToolsPrompt() correctly filters MCP tool schemas shown to the model by both
mcpServer.enabled and per-tool enabled status. However, runMcpTool() — the
function that actually executes a tool call — does not re-apply this filter. It
resolves the target server purely by matching the requested tool name:
val serverState = mcpManagerViewModel.uiState.value.mcpServers.find { serverState ->
serverState.mcpServer.toolsList.any { it.name == toolName }
}
// missing: .filter { it.mcpServer.enabled } / .filter { it.enabled }
Impact
If the model calls a tool name belonging to a server/tool the user has explicitly
disabled in the UI — e.g. via stale conversation context, or hallucination — the
call is still executed. This breaks the implicit UI contract that "Disabled" means
the server receives no further calls, and can lead to unintended data transmission
or actions on a server the user actively revoked permission for (e.g. a user with
both a personal and a work MCP server who disables the work server for a private
session).
Not a remote attack vector — requires the user to have configured the affected
server themselves — but a privacy-relevant access-control gap.
Steps to reproduce
- Configure two MCP servers, both with overlapping/similarly-named tools.
- Disable one server (or one tool) via the UI toggle.
- In a session where the model has prior context referencing the disabled
tool's name (or via a crafted/hallucinated call), have the model call that
tool name.
- Observe: the call executes against the disabled server instead of being
rejected.
Suggested fix
Apply the same enabled filter used in getToolsPrompt() (.filter { it.mcpServer.enabled },
.filter { it.enabled }) inside runMcpTool() before resolving/executing the call,
and return a clear error if the resolved tool is disabled.
Related issues checked (none cover this)
Environment
Reproduced on current main.
Summary
getToolsPrompt()correctly filters MCP tool schemas shown to the model by bothmcpServer.enabledand per-toolenabledstatus. However,runMcpTool()— thefunction that actually executes a tool call — does not re-apply this filter. It
resolves the target server purely by matching the requested tool name:
Impact
If the model calls a tool name belonging to a server/tool the user has explicitly
disabled in the UI — e.g. via stale conversation context, or hallucination — the
call is still executed. This breaks the implicit UI contract that "Disabled" means
the server receives no further calls, and can lead to unintended data transmission
or actions on a server the user actively revoked permission for (e.g. a user with
both a personal and a work MCP server who disables the work server for a private
session).
Not a remote attack vector — requires the user to have configured the affected
server themselves — but a privacy-relevant access-control gap.
Steps to reproduce
tool's name (or via a crafted/hallucinated call), have the model call that
tool name.
rejected.
Suggested fix
Apply the same
enabledfilter used ingetToolsPrompt()(.filter { it.mcpServer.enabled },.filter { it.enabled }) insiderunMcpTool()before resolving/executing the call,and return a clear error if the resolved tool is disabled.
Related issues checked (none cover this)
the permission prompt only fires after the disabled server is already resolved,
and
alwaysAllowbypasses it entirely)Environment
Reproduced on current
main.