Skip to content

discover_tools can never work: server sends tools/list_changed without declaring tools.listChanged #649

Description

@ichoosetoaccept

Environment

Server version @zereight/mcp-gitlab 2.1.46 (latest release; bug also present on main)
Client Claude Code 2.1.229 — plus a raw JSON-RPC probe, below, to isolate it from the client
Transport stdio
Env vars GITLAB_TOOLSETS=pipelines, GITLAB_API_URL, GITLAB_PERSONAL_ACCESS_TOKEN (self-hosted GitLab)
Node 22.21.1

discover_tools activates a toolset server-side and returns success, but no spec-conformant MCP client will ever pick up the new tools — because the server never declares the capability that makes notifications/tools/list_changed meaningful.

This is the root cause of #477, which was closed as a VS Code / Roo Code caching problem with "not something gitlab-mcp can fix by only changing server behavior". It is fixable by changing server behaviour, in one line.

The declaration

index.ts:647-649 (current main, and shipped in v2.1.46):

capabilities: {
  tools: {},
},

Per the spec, a server that changes its tool list must declare tools: { listChanged: true }. Clients are only obliged to honour notifications/tools/list_changed from a server that advertised it, so the notification sent at index.ts (build/index.js:307) is dropped.

Steps to reproduce

  1. Start the server over stdio with GITLAB_TOOLSETS=pipelines.
  2. Send initialize, then notifications/initialized.
  3. Send tools/call for discover_tools with {"category": "issues"}.
  4. Read the frames the server writes back.

Result — the capability is absent from the handshake, yet the notification is sent:

← initialize result:  "capabilities":{"tools":{}}                      # listChanged not declared
← after discover_tools: {"method":"notifications/tools/list_changed"}  # sent regardless

So the server both (a) fails to advertise the capability and (b) emits the notification anyway.

Observed in Claude Code 2.1.229: discover_tools returns {"activated":"issues","totalTools":44}, and the very next call fails with No such tool available: mcp__gitlab__list_issues. The client does implement notifications/tools/list_changed; it correctly ignores one from a server that never declared support.

Why the SDK doesn't catch it

Server.sendToolListChanged() is guarded by assertNotificationCapability, but the check is one level too coarse:

case 'notifications/tools/list_changed':
  if (!this._capabilities.tools) { throw ... }

{} is truthy, so the guard passes. The try {} catch {} around the call in discover_tools ("Client may not support notifications - safe to ignore") then ensures nothing surfaces even if it did throw — the tool reports success either way.

Fix

capabilities: {
  tools: { listChanged: true },
},

Worth considering alongside it: dropping the bare catch {} so a genuine notification failure is visible rather than reported as {"activated": ...}.

Happy to open a PR if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions