Switched-off tools still appear in context via mcp list/connect/description

Open
#336 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@Piggidragon is already working on this.

Since Nov 1, 2026.

  • #350 by @Piggidragon — open

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
57/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript

Research direction

Start with core/server/src/pi/guard.ts and core/server/src/mcp-offer.ts, tracing how the switch set from offFor() or toolDefaultsForSession() reaches list, connect, search, and prompt-description paths. Check the existing coverage for those paths and for mcp_script; done means off tool names and descriptions never reach model context, fully switched-off servers are not offered for connection, and codemode still blocks them.

Written by the indexing model from the issue text.

Description

bug

What happens now

When a tool (including any MCP server's tools) is switched off for a chat, the calls are refused — switchedOffVia() in core/server/src/pi/guard.ts blocks mcp({tool:...}), describe, direct calls and the tools gateway. But the tool still appears in the model's context:

  1. mcp({ server: "name" }) lists every cached tool of the server, off ones included, with descriptions.
  2. mcp({ connect: "name" }) connects a switched-off server and hands back its full tool list.
  3. The bare mcp({}) status call reports "N tools (cached)" per server.
  4. The mcp meta-tool's description lists the servers and their direct tools regardless of switches ("Direct tools available (call as normal tools): understory (5), nextcloud_uni (27)").
  5. A switched-off MCP tool therefore can be listed, its parameters learned via describe, and a "refused: switched off" result is all that stands between the model and it — the refusal even tells the model the exact name it could try elsewhere.

The current split is deliberate (see the doc comment above switchedOffVia(): "a search or a list ... hands back what every server says"), but for the common case — "this tool does not exist in this chat" — off should mean gone, not visible-but-refused.

Expected behavior

If a tool is switched off for a chat, it should not surface at all:

  • mcp({ server: "name" }) lists only the tools that are on (or reports the server as off when none of its tools are).
  • mcp({ connect: "name" }) either refuses to connect a fully-off server or connects without advertising its off tools.
  • The mcp tool's prompt description ("servers", "N tools", "Direct tools available") is built from what is actually offered in this conversation, not from the adapter cache.
  • describe keeps refusing (already does).

The switches are known to the portal already (offFor() / toolDefaultsForSession() / switchedOffVia() in guard.ts, catalogue via mcpCatalogue() in core/server/src/mcp-offer.ts), so filtering the list/connect/description paths is a question of applying the same off set that call-refusal already uses.

Reproduction

  1. Configure an MCP server (e.g. nextcloud_uni, 27 tools) with tools reachable through the mcp gateway.
  2. Switch all its tools off in Settings → Tools (or per chat/project).
  3. In a chat where they are off: mcp({ server: "nextcloud_uni" }) → lists all 27 with descriptions; mcp({ connect: "nextcloud_uni" }) → connects and re-lists them.
  4. Any actual call is refused: "Refused: 'nextcloud_uni_read_nc_get_capabilities' is switched off in this conversation, and the mcp tool does not reach it either."

So: unusable, but fully present in context.

Acceptance criteria

  • No switched-off tool name or description reaches the model's prompt (system prompt, mcp tool description, list/connect/search results) in a chat where it is off.
  • A server whose tools are all off is not offered for connect by default.
  • Codemode scripts (mcp_script) already cannot reach switched-off tools — keep that true.
Dominant language
TypeScript
Stars
297
Forks
81
Avg merge
8h 47m
Merged PRs (30d)
167

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from thecodacus/pithagoras

All issues in thecodacus/pithagoras

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.