MCP server's tools never reach the model when server advertises tools capability without listChanged (sage shows green, 0 tools enrolled)
Nobody has claimed this yet.
- 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
- Configure an MCP server that, at
initialize, advertises its capabilities as{"tools": {}}— thetoolscapability is present but the optionallistChangedsub-property is omitted. (Per the MCP spec,listChangedis optional.) - Start a ZCode session. The server connects and displays green/healthy in the MCP panel.
- Inspect the request ZCode sends to the model (the session rollout at
~/.zcode/cli/rollout/model-io-sess_*.jsonl). Look at thetoolsarray.
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__sageenrollment,tool_search_callinvocations of sage tools, and a returned tool result. (Seeevidence-codex-sage-works.json.) - The reference
@modelcontextprotocol/sdklistTools()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.json → mcp.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: 9inclsage, 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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