Skip to content

fix(cli): ignore case in --exclude-tools and --tools, reject unknown names - #617

Open
tanishqbaweja wants to merge 1 commit into
CursorTouch:mainfrom
tanishqbaweja:fix/tool-filter-names
Open

tanishqbaweja wants to merge 1 commit into
CursorTouch:mainfrom
tanishqbaweja:fix/tool-filter-names

Conversation

@tanishqbaweja

Copy link
Copy Markdown
Contributor

Problem

--exclude-tools is how a deployment switches off tools it does not need, such as the README's windows-mcp serve --exclude-tools "PowerShell,Registry". _apply_tool_filter() compares the names it gets with the registered tool names exactly, and skips a name that matches none without a word. That holds for --tools, --exclude-tools, WINDOWS_MCP_TOOLS, WINDOWS_MCP_EXCLUDE_TOOLS and [tools] exclude in config.toml. So:

  • --exclude-tools powershell (or Powershell, or a typo such as PowerShel) starts the server with the PowerShell tool enabled.
  • --tools "screenshot,click" starts it with ControlStatus alone, and --tools "Screenshot,Clik" without Click.

The flags came with the tier system (65f7279), whose normalize_tool_names() in tiers.py matched names ignoring case and stopped startup on an unknown name with Unknown tools: ... Allowed: .... 5c12628 replaced it the same day with the inline filter, which does neither.

Reproduction

At 106b50c on Windows 11 (build 22631) with Python 3.14.9, fastmcp 4.1.3 and click 8.5.0. A script ran windows-mcp serve through click's CliRunner with the real _build_mcp(), so the real tool registry, and FastMCP.run replaced, so no server started; then it listed the registered tools. On main every command starts the server and prints nothing:

serve --exclude-tools PowerShell             20 tools, PowerShell removed
serve --exclude-tools powershell             21 tools, PowerShell still registered
serve --exclude-tools "Powershell,registry"  21 tools, PowerShell and Registry still registered
serve --exclude-tools PowerShel              21 tools
serve --tools "screenshot,click"             1 tool: ControlStatus
serve --tools "Screenshot,Clik"              2 tools: ControlStatus, Screenshot

Fix

__main__.py: _apply_tool_filter() matches names ignoring case, and raises the old check's error, as a click.ClickException, for a name that matches no registered tool. The reproduction then gives:

serve --exclude-tools PowerShell             20 tools, PowerShell removed
serve --exclude-tools powershell             20 tools, PowerShell removed
serve --exclude-tools "Powershell,registry"  19 tools, PowerShell and Registry removed
serve --exclude-tools PowerShel              exit code 1, server not started
serve --tools "screenshot,click"             3 tools: Click, ControlStatus, Screenshot
serve --tools "Screenshot,Clik"              exit code 1, server not started

The typo prints:

Error: Unknown tools: PowerShel. Allowed: App, Click, Clipboard, ControlStatus, DisplayInventory, FileSystem, Move, MultiEdit, MultiSelect, Notification, PowerShell, Process, Registry, Scrape, Screenshot, Scroll, Shortcut, Snapshot, Type, Wait, WaitFor
  • An unknown name stops startup the way other bad settings do (--ssl-certfile without --ssl-keyfile, an invalid ip_allowlist). serve() passes a ClickException from _run_server() through without logging it as an unhandled exception, so the error prints on its own, not after a "Server exiting due to unhandled exception" traceback.
  • Exact names work as before. ControlStatus is still always kept, and --tools still overrides the exclusions, which are then not checked since they are not used.
  • README's Tool Selection section now says names are not case-sensitive and an unknown name stops the server.

Tests

New tests/test_tool_filter.py, on a FastMCP with four tools:

  • test_exclude_matches_tool_names_ignoring_case: PowerShell, powershell and POWERSHELL each remove PowerShell.
  • test_tools_match_tool_names_ignoring_case: Screenshot,Click and screenshot,CLICK keep the same tools.
  • test_unknown_tool_name_is_an_error: an unknown exclusion, an unknown name in the tool list, and two unknown names. The error lists them and no tool is removed.
  • test_unknown_tool_name_never_starts_server: serve through CliRunner, with FastMCP.run replaced, and the name in --exclude-tools, in WINDOWS_MCP_EXCLUDE_TOOLS or in [tools] exclude. Exit code 1, the output is the one error line, the server is not started and nothing logs "Server exiting".

I ran each file on its own, in the same environment:

python -m pytest tests/test_tool_filter.py -v
python -m pytest tests/test_control_mcp_gate.py
python -m pytest tests/test_cli_paired_flags.py
python -m pytest tests/test_control_cli.py
python -m pytest tests/test_cli_debug.py
python -m pytest tests/test_cli_legacy_flags.py
python -m pytest tests/test_config_stateless.py

On main the new file has 9 failures; only the 2 exact-case cases pass. The lowercase cases fail on the tool set (Extra items in the left set: 'PowerShell', assert {'ControlStatus'} == ...), the unknown names with DID NOT RAISE ClickException, and the serve cases with assert 0 == 1 on the exit code. On this branch all 11 pass. Without the pass-through in serve(), the 3 serve cases fail on the log check. The other files pass on main and on this branch with the same counts: test_control_mcp_gate.py (15 passed, with its ControlStatus filter test), test_cli_paired_flags.py (5), test_control_cli.py (25), test_cli_debug.py (10), test_cli_legacy_flags.py (11) and test_config_stateless.py (9). I did not run the whole suite.

ruff check and ruff format --check are clean on both changed Python files.

Not tested

A real MCP client: no server was started. To check it on a real machine, start windows-mcp serve --exclude-tools powershell from a client and list its tools: on main PowerShell is listed, with this change it is not. windows-mcp serve --exclude-tools PowerShel should exit at once with the error above.

…names

_apply_tool_filter() compares the names from --tools, --exclude-tools,
WINDOWS_MCP_TOOLS, WINDOWS_MCP_EXCLUDE_TOOLS and [tools] exclude with
the registered tool names exactly, and skips a name that matches none.
So `serve --exclude-tools powershell` (or Powershell, or a typo such as
PowerShel) starts with the PowerShell tool enabled and says nothing,
and `serve --tools "screenshot,click"` starts with ControlStatus alone.
The flags came with the tier system (65f7279), whose
normalize_tool_names() matched names ignoring case and stopped startup
on an unknown one; 5c12628 replaced it with this filter.

Match names ignoring case, and raise that check's error for a name
that matches no registered tool, so serve exits with code 1 and
"Error: Unknown tools: PowerShel. Allowed: App, Click, ..." instead of
starting. serve() passes a ClickException from _run_server() through
without logging it as an unhandled exception, so the error prints like
the other startup errors, without a traceback. Exact names, the
reserved ControlStatus and --tools overriding the exclusions are
unchanged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant