zai-org / zai-org/feedback

MCP server's tools never reach the model when server advertises tools capability without listChanged (sage shows green, 0 tools enrolled)

Open
#38 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

priority: P2
Dominant language
No language data
Stars
22
Forks
1
PR merge metrics
No merged PRs in 30d

Description

zcode-sage-mcp-issue-attachments.zip

Summary

An MCP server's tools never reach the model when the server advertises its tools capability without the listChanged sub-property. The server connects and shows green/healthy in the UI, but zero of its tools are enrolled into the model request. From the user's perspective the server "works" but does nothing.

Environment

  • OS: macOS 26.5.1 (25F80), Apple Silicon
  • ZCode: 3.1.2 (3.1.2.1788)
  • Model channel: Z.ai — GLM-5.2
  • Affected server: sage — Streamable HTTP MCP server, bearer auth
  • Reference (working) client: Codex Desktop on the same machine, connecting to the same sage endpoint — enrolls and calls all 20 sage tools correctly.

Steps to reproduce

  1. Configure an MCP server that, at initialize, advertises its capabilities as {"tools": {}} — the tools capability is present but the optional listChanged sub-property is omitted. (Per the MCP spec, listChanged is optional.)
  2. Start a ZCode session. The server connects and displays green/healthy in the MCP panel.
  3. Inspect the request ZCode sends to the model (the session rollout at ~/.zcode/cli/rollout/model-io-sess_*.jsonl). Look at the tools array.

Expected vs. actual behavior

Expected Actual
Server status in UI Connected Connected ✔
Tools enrolled to model 20 (mcp__sage__*) 0
Model rollout tools array contains sage entries no sage entries, ever

Across 128 model requests in one session, 0 contained any sage tool. The other 7 configured servers' tools were present in every request. (See evidence-zcode-no-sage-tools.json in the attached archive.)

Root cause (isolated)

ZCode does not enroll a server's tools when tools.listChanged is absent from the server's advertised capabilities. Every other server I have configured advertises {"tools": {"listChanged": true}} and its tools enroll correctly. sage advertises the bare {"tools": {}} and its tools are silently dropped — the server still shows green because the tools capability itself is present.

This is a client-side issue, confirmed by cross-client comparison:

  • Codex Desktop, connecting to the identical sage endpoint, enrolls mcp__sage (as a namespace wrapper containing all 20 tools) and the model successfully calls them. Verified in Codex's rollout: mcp__sage enrollment, tool_search_call invocations of sage tools, and a returned tool result. (See evidence-codex-sage-works.json.)
  • The reference @modelcontextprotocol/sdk listTools() against the same server returns all 20 tools with no validation errors.

So the server is MCP-compliant; the gap is in ZCode's tool-enrollment logic, which appears to gate on listChanged being present even though that property is optional per spec.

Capability correlation across all 9 configured servers (perfect 7-vs-1)
sage              tools cap: {}                      → 0 tools enrolled  ✘
context7          tools cap: {"listChanged":true}    → enrolled          ✔
tavily            tools cap: {"listChanged":true}    → enrolled          ✔
zread             tools cap: {"listChanged":true}    → enrolled          ✔
web-reader        tools cap: {"listChanged":true}    → enrolled          ✔
web-search-prime  tools cap: {"listChanged":true}    → enrolled          ✔
Paste.app         tools cap: {"listChanged":true}    → enrolled          ✔
zai-mcp-server    (stdio)                            → enrolled          ✔
node_repl         (stdio)                            → enrolled          ✔

Logs & evidence

A full archive with host logs and per-request evidence is available on request (GitHub's API does not support programmatic issue-attachment upload; the zip can be attached via the web UI). The key evidence is inlined below.

Redacted MCP config (~/.zcode/cli/config.jsonmcp.servers, all tokens redacted)
{
  "mcp": {
    "servers": {
      "context7": {
        "http_headers": {
          "CONTEXT7_API_KEY": "***REDACTED***"
        },
        "type": "http",
        "url": "https://mcp.context7.com/mcp"
      },
      "node_repl": {
        "args": [],
        "command": "/Applications/Codex.app/Contents/Resources/cua_node/bin/node_repl",
        "env": {
          "BROWSER_USE_AVAILABLE_BACKENDS": "***REDACTED***",
          "BROWSER_USE_CODEX_APP_BUILD_FLAVOR": "***REDACTED***",
          "BROWSER_USE_CODEX_APP_VERSION": "***REDACTED***",
          "CODEX_CLI_PATH": "***REDACTED***",
          "CODEX_HOME": "***REDACTED***",
          "NODE_REPL_INSTRUCTIONS_USE_CASE_BROWSER": "***REDACTED***",
          "NODE_REPL_NATIVE_PIPE_CONNECT_TIMEOUT_MS": "***REDACTED***",
          "NODE_REPL_NODE_MODULE_DIRS": "***REDACTED***",
          "NODE_REPL_NODE_PATH": "***REDACTED***",
          "NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S": "***REDACTED***",
          "NODE_REPL_TRUSTED_CODE_PATHS": "***REDACTED***"
        },
        "startup_timeout_sec": 120,
        "type": "stdio"
      },
      "Paste.app": {
        "headers": {
          "Authorization": "***REDACTED***"
        },
        "type": "http",
        "url": "http://127.0.0.1:39725/mcp"
      },
      "sage": {
        "enabled": true,
        "headers": {
          "Authorization": "***REDACTED***"
        },
        "type": "http",
        "url": "https://sage.stands-macbook-pro.local:7244/v1/mcp/streamable"
      },
      "tavily-remote-mcp": {
        "enabled": true,
        "type": "http",
        "url": "https://mcp.tavily.com/mcp/?tavilyApiKey=***REDACTED***"
      },
      "web-reader": {
        "headers": {
          "Authorization": "***REDACTED***"
        },
        "type": "http",
        "url": "https://api.z.ai/api/mcp/web_reader/mcp"
      },
      "web-search-prime": {
        "headers": {
          "Authorization": "***REDACTED***"
        },
        "type": "http",
        "url": "https://api.z.ai/api/mcp/web_search_prime/mcp"
      },
      "zai-mcp-server": {
        "args": [
          "-y",
          "@z_ai/mcp-server"
        ],
        "command": "npx",
        "environment": {
          "Z_AI_API_KEY": "***REDACTED***",
          "Z_AI_MODE": "***REDACTED***"
        },
        "type": "stdio"
      },
      "zread": {
        "headers": {
          "Authorization": "***REDACTED***"
        },
        "type": "http",
        "url": "https://api.z.ai/api/mcp/zread/mcp"
      }
    }
  }
}
ZCode session evidence — per-request tool summary (extracted from rollout)
{
  "source": "ZCode rollout (sess_9b0721cc-90f8-4987-a73d-311e3de7b307)",
  "what_this_proves": "Across every model request in the session, the tools array contains 0 sage tools. The other 7 MCP servers namespaces are present. The sage server shows green/connected in the UI and host logs.",
  "total_requests_with_tools_array": 128,
  "requests_with_any_sage_tool": 0,
  "all_distinct_namespaces_ever_seen": [
    "Paste_app",
    "context7",
    "tavily-remote-mcp",
    "web-reader",
    "web-search-prime",
    "zai-mcp-server",
    "zread"
  ],
  "note": "Full per-request breakdown available in attached archive (evidence-zcode-no-sage-tools.json)."
}
Codex reference evidence — same server, tools DO enroll (extracted from rollout)
{
  "source": "Codex Desktop rollout (same machine, same sage server)",
  "what_this_proves": "Codex connects to the identical sage endpoint and enrolls all 20 sage tools (as a namespace wrapper), then invokes them. Proves the server is healthy and the failure is ZCode-client-specific.",
  "total_sage_related_lines": 25,
  "sample_evidence_lines": [
    {
      "line": 10,
      "kind": "tool_search_call (invoking a sage tool)"
    },
    {
      "line": 11,
      "kind": "enrollment (namespace wrapper with sage tools)"
    },
    {
      "line": 13,
      "kind": "unknown"
    },
    {
      "line": 14,
      "kind": "unknown"
    },
    {
      "line": 15,
      "kind": "tool_result"
    },
    {
      "line": 18,
      "kind": "tool_search_call (invoking a sage tool)"
    }
  ],
  "note": "Full list (25 lines) in attached archive."
}

Files in the archive (zcode-sage-mcp-issue-attachments.zip, 44 KB, all credentials redacted):

  • zcode-host-log-2026-06-23.log — host logs; mcpServerCount: 9 incl sage, zero MCP errors.
  • evidence-zcode-no-sage-tools.json — full per-request breakdown.
  • evidence-codex-sage-works.json — full Codex evidence.
  • zcode-mcp-config-redacted.json — redacted config.
  • README.txt — index.

Suggested fix

In the MCP tool-enrollment path, treat a server as tools-capable when the tools capability is present (regardless of whether listChanged is set), and enroll its tools from tools/list. Per the MCP spec, listChanged only governs whether the client should re-fetch the list upon receiving a notifications/tools/list_changed notification; its absence should not prevent initial enrollment.

Severity

An entire configured MCP server's toolset is silently dropped with no error surfaced to the user — the UI reports green. This is effectively invisible capability loss.

Contributor guide

Open the contributing guide

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.

Research direction

Reproduce with an MCP server advertising tools as {} and inspect the model rollout at ~/.zcode/cli/rollout/model-io-sess_*.jsonl, comparing the tools array with evidence-zcode-no-sage-tools.json. Trace the MCP tool-enrollment path and verify that tools/list is used when the tools capability is present without listChanged; done when the server's tools appear in model requests without errors.

Written by the indexing model from the issue text.

Assessment

Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.