Skip to content

fix!: dispatch only tools the tools list declares - #30

Merged
Martin Bens (SpiGAndromeda) merged 1 commit into
mainfrom
fix/tools-call-declared-tools-only
Oct 1, 2026
Merged

Martin Bens (SpiGAndromeda) merged 1 commit into
mainfrom
fix/tools-call-declared-tools-only

Conversation

@SpiGAndromeda

@SpiGAndromeda Martin Bens (SpiGAndromeda) commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #29.

Problem

handle_tools_call decided that a tool existed by checking for a shell function tool_<name> with type. validate_tool_arguments returned success for a name with no tools-list entry. A sourced function the server never declared was therefore dispatched with its arguments unchecked. Every tool_<name>_cancel hook also answered as tool <name>_cancel. type also matches executables, so a tool_x on PATH ran as a tool or as a cancel hook.

Behavior

The tools list decides which tools exist. handle_tools_call checks in this order:

  1. params object gate → -32602 (unchanged)
  2. name pattern → -32602 Invalid tool name (unchanged)
  3. _mcp_tool_schema reads the tools list once:
    • undeclared name → -32601 Tool not found: <name>
    • unreadable list, duplicate entry, or absent/null inputSchema → isError result
  4. declare -F tool_<name> → -32601 when no function exists
  5. _mcp_validate_against_schema checks the arguments against the schema from step 3 → isError on violation
  6. dispatch

The lookup uses the same .tools[]? iteration as before, so the declaration check and the validation read the list the same way. _run_cancel_hook also resolves hooks with declare -F. A tool function can still call another tool_* function directly in shell. A nested handle_tools_call follows the declaration rule.

Compatibility

  • An undeclared tool_<name> function, including a cancel hook called as a tool, answers -32601 instead of running.
  • A declared entry with no or a null inputSchema answers isError instead of dispatching unvalidated. README previously documented the unvalidated dispatch.
  • validate_tool_arguments keeps its signature and returns 1 for an undeclared name, where it returned 0.
  • A name declared twice now answers with its own message instead of they could not be evaluated against its schema.

AGENTS.md §Compatibility contract classifies tightening the validator as major. The [Unreleased] CHANGELOG entry carries no version marker, so the bump is decided at release. Consumers should declare every tool their clients call in tools.json with an inputSchema before bumping the pin.

handle_tools_call decided that a tool existed by checking for a shell function named tool_<name> with `type`, and validate_tool_arguments returned success for a name it found no tools-list entry for. A sourced function the server never declared was therefore dispatched with its arguments unchecked, and every tool_<name>_cancel hook answered as tool <name>_cancel. A consumer that sources shared tool libraries into several servers had to unset the undeclared functions at startup to close this (#29).

The new internal _mcp_tool_schema reads the tools list once per call and returns the declared entry's schema. _mcp_validate_against_schema, the validator body moved out of validate_tool_arguments, checks the arguments against that schema. An undeclared name answers -32601 before any function lookup. An unreadable list, a duplicate entry, or an entry with no inputSchema answers isError. Tool and cancel-hook lookups use declare -F, so a PATH executable named tool_x no longer runs. tests/cancellation.bats now declares the five tools its in-process cases dispatch.

BREAKING CHANGE: an undeclared tool_<name> function, including a cancel hook called as a tool, answers -32601 instead of running. A declared entry with no or a null inputSchema answers isError instead of dispatching unvalidated. validate_tool_arguments returns 1 for an undeclared name. Declare every tool a client calls in tools.json with an inputSchema before bumping the pin. A tool function may still call another tool_* function directly in shell.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SpiGAndromeda
Martin Bens (SpiGAndromeda) merged commit 00bc689 into main Oct 1, 2026
3 checks passed
@SpiGAndromeda
Martin Bens (SpiGAndromeda) deleted the fix/tools-call-declared-tools-only branch October 1, 2026 18:44
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.

tools/call runs any tool_* function in scope, declared or not, and validates an undeclared one against nothing

1 participant