fix(desktop): treat an explicit empty MCP include list as block-all
`isToolEnabled` used `include?.length`, so `tools.include: []` (the block-all whitelist the runtime honours) rendered every tool as enabled, and toggling the last include entry off deleted the key, silently flipping the server back to "all tools". Keep the empty list. Salvaged from #52874 (desktop portion) by @Bartok9. Part of #12865.
This commit is contained in:
@@ -31,6 +31,13 @@ describe('isToolEnabled', () => {
|
||||
expect(isToolEnabled(server, 'a')).toBe(true)
|
||||
expect(isToolEnabled(server, 'b')).toBe(false)
|
||||
})
|
||||
|
||||
it('empty include blocks every tool (block-all, not all-on)', () => {
|
||||
const server = { tools: { include: [] as string[] } }
|
||||
expect(isToolEnabled(server, 'a')).toBe(false)
|
||||
expect(isToolEnabled(server, 'b')).toBe(false)
|
||||
expect(countEnabledTools(server, ['a', 'b', 'c'])).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toggleToolInServer', () => {
|
||||
@@ -49,6 +56,12 @@ describe('toggleToolInServer', () => {
|
||||
expect(next.tools).toEqual({ include: ['b'] })
|
||||
})
|
||||
|
||||
it('retains empty include when last include tool is toggled off', () => {
|
||||
const next = toggleToolInServer({ tools: { include: ['a'] } }, 'a')
|
||||
expect(next.tools).toEqual({ include: [] })
|
||||
expect(isToolEnabled(next, 'a')).toBe(false)
|
||||
})
|
||||
|
||||
it('respects include mode: re-enabling adds back to include', () => {
|
||||
const next = toggleToolInServer({ tools: { include: ['b'] } }, 'a')
|
||||
expect(next.tools).toEqual({ include: ['b', 'a'] })
|
||||
|
||||
@@ -28,22 +28,30 @@ export function readToolsFilter(server: ServerConfig | null | undefined): McpToo
|
||||
export function isToolEnabled(server: ServerConfig | null | undefined, name: string): boolean {
|
||||
const { exclude, include } = readToolsFilter(server)
|
||||
|
||||
return include?.length ? include.includes(name) : !exclude?.includes(name)
|
||||
// An explicit `include` (even []) is a whitelist — the runtime registers nothing for `[]`
|
||||
// (tools/mcp_tool_registration.py), so the desktop must not show every tool as enabled (#12865).
|
||||
if (include !== undefined) {
|
||||
return include.includes(name)
|
||||
}
|
||||
|
||||
return !exclude?.includes(name)
|
||||
}
|
||||
|
||||
// Toggle one tool, preserving the config's mode (include if present, else an
|
||||
// exclude denylist). Empty lists — and an emptied `tools` — are dropped.
|
||||
// Toggle one tool, preserving the config's mode (include if the key is present, even empty, else
|
||||
// an exclude denylist). An emptied exclude is dropped; an emptied include is kept (block-all).
|
||||
export function toggleToolInServer(server: ServerConfig, name: string): ServerConfig {
|
||||
const { exclude, include } = readToolsFilter(server)
|
||||
const key = include?.length ? 'include' : 'exclude'
|
||||
const key = include !== undefined ? 'include' : 'exclude'
|
||||
const current = (key === 'include' ? include : exclude) ?? []
|
||||
const names = current.includes(name) ? current.filter(n => n !== name) : [...current, name]
|
||||
const tools = { ...toolsObject(server) }
|
||||
|
||||
if (names.length) {
|
||||
tools[key] = names
|
||||
if (key === 'include') {
|
||||
tools.include = names
|
||||
} else if (names.length) {
|
||||
tools.exclude = names
|
||||
} else {
|
||||
delete tools[key]
|
||||
delete tools.exclude
|
||||
}
|
||||
|
||||
const next = { ...server }
|
||||
|
||||
Reference in New Issue
Block a user