Skip to content

fix: keep the tools capability when canAccess hides every tool - #371

Merged
punkpeye merged 1 commit into
punkpeye:mainfrom
L4XB:fix/tools-capability-for-filtered-sessions
Sep 11, 2026
Merged

fix: keep the tools capability when canAccess hides every tool#371
punkpeye merged 1 commit into
punkpeye:mainfrom
L4XB:fix/tools-capability-for-filtered-sessions

Conversation

@L4XB

@L4XB L4XB commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #370.

FastMCPSession declared the tools capability and registered the tools/list / tools/call handlers only when its own tool list was non-empty. That list is already canAccess-filtered by #createSession, so a server that has tools but shows none of them to one session did not declare the capability for that session. The spec requires it ("Servers that support tools MUST declare the tools capability"), and two things broke:

  • tools/list answered -32601 Method not found instead of an empty list, so a client could not tell a tool-less server from one whose tools it may not see;
  • addTool() threw Server does not support tools (required for tools/list) while such a session was connected, because toolsListChangedsetupToolHandlers registers a handler for a capability that session never declared.

Same class as #208/#209 (completions) and #327 (resource templates): a handler gated on the filtered/derived list instead of on what the server supports.

Change

src/FastMCP.ts

  • FastMCPSession takes an optional hasTools ("the server has tools, including ones this session's canAccess filtering removed"), defaulting to tools.length > 0 so every other caller is unchanged.
  • The capability and the handler registration are gated on that instead of on the filtered list. setupToolHandlers still receives the filtered list, so a session that may not see a tool cannot list or call it (an empty list, and Unknown tool on call).
  • #createSession passes hasTools: this.#tools.length > 0. The stdio path passes the unfiltered list already.

A server without any tools still does not advertise the capability.

Tests

src/FastMCP.can-access-empty.test.ts: a role: user session on a server with one admin-only tool gets the tools capability and an empty tools/list; addTool() no longer throws while that session is connected and the new public tool shows up on the next list; an admin session still sees the tool; a tool-less server still advertises nothing. The first two fail on main.

vitest run src/FastMCP.test.ts src/FastMCP.in-memory.test.ts src/FastMCP.sse-auth.test.ts src/FastMCP.can-access-empty.test.ts   → 129 passed
prettier --check, eslint, tsc --noEmit                                                                                             → clean

A session declared the tools capability and registered the tools/list
and tools/call handlers only when its own, already canAccess-filtered
tool list was non-empty. A server that has tools but shows none of them
to one session therefore did not declare the capability for it:
tools/list answered -32601 instead of an empty list, and addTool() threw
"Server does not support tools" while such a session was connected.

The capability describes the server, not the session. Pass whether the
server has any tools alongside the filtered list, and gate the
capability and the handlers on that. The handlers work on the filtered
list as before, so a session that may not see a tool still cannot list
or call it.

Fixes punkpeye#370
@punkpeye
punkpeye merged commit 0347f1a into punkpeye:main Sep 11, 2026
2 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 4.20.10 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

canAccess filtering all tools out of a session drops the tools capability, so tools/list is -32601 and addTool() throws

2 participants