Summary
A self-hosted MCP server (Streamable HTTP) connects fine through the standalone @modelcontextprotocol/sdk (the same version ZCode bundles, ^1.26.0), but ZCode reports fetch failed for the same server with the same token. The decisive finding: when ZCode is pointed at a capture proxy, no request reaches the proxy at all — ZCode fails before making any network call, inside the process, while the standalone SDK against the same proxy sends all requests and connects.
Environment
| Item |
Value |
| ZCode |
3.3.6 (3.3.6.3198) |
| Runtime |
Electron 41.0.3, macOS 15 (darwin 25.5.0 arm64) |
| MCP SDK (bundled) |
@modelcontextprotocol/sdk@^1.26.0 |
| MCP server |
self-hosted, Streamable HTTP (single endpoint /mcp), returns mcp-session-id + text/event-stream |
| Transport |
type: "http", headers.Authorization: Bearer … |
Reproduce
1. The server is healthy (verified independently)
POST /mcp (initialize) → 200, mcp-session-id: <uuid>, content-type: text/event-stream
POST /mcp (notifications/init)→ 202
GET /mcp (with session-id) → 200, text/event-stream (server→client stream)
POST /mcp (tools/list) → 200
2. The standalone SDK connects (same SDK ZCode bundles)
Running @modelcontextprotocol/sdk@^1.26.0 in plain Node against the server:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(new URL("https://<host>/mcp"), {
requestInit: { headers: { Authorization: "Bearer <token>" } },
});
const client = new Client({ name: "diag", version: "0" }, { capabilities: {} });
await client.connect(transport);
console.log(await client.listTools()); // → 16 tools
✅ connected — 16 tools (stable across 3/3 runs)
3. ZCode fails with no network call
I pointed the ZCode ai-memory server at a capture proxy (http://127.0.0.1:3129/mcp) that forwards to the real server and logs every request, then restarted ZCode.
- The standalone SDK through the proxy → all 4 requests captured (initialize → 200, notifications → 202, GET stream, tools/list → 200), connects, 16 tools.
- ZCode through the proxy → 0 requests captured,
fetch failed.
So ZCode's MCP client throws fetch failed without ever opening a connection to the server/proxy. This is not a network/TLS/CA/token issue (the standalone SDK rules all of that out) and not an SDK-version issue (same ^1.26.0). It's specific to how ZCode invokes the SDK inside the Electron process.
ZCode log
{
"event": "mcp.server.failed",
"level": "warn",
"message": "MCP server connection failed",
"context": {
"error": "fetch failed",
"mcpServerName": "<name>",
"transport": "http"
},
"durationMs": 90
}
Failures are consistently 44–185 ms — far below any network/TLS timeout and below the 30 s tool timeout — consistent with a synchronous, pre-network failure rather than a network timeout.
Two problems
- Pre-network failure: ZCode throws
fetch failed before sending any request, while the same SDK in plain Node works against the same server. Something in how ZCode builds or dispatches the Streamable HTTP request fails (or is aborted) before reaching the network stack.
- Opaque error: The UI and log show only
fetch failed — the error.cause / code from the SDK (which undici nests) is never serialized. Whether the root cause is a header issue, a URL parsing issue, an abort, or something else, there is no way to tell from ZCode's output. At minimum, surfacing error.cause would make cases like this self-diagnosing.
Expected
- ZCode should reach the same result as the standalone SDK against the same server (connect, list tools).
- When the MCP client fails, log/show the real cause (
error.cause, error code), not just fetch failed.
Notes
- TLS/CA is not the issue:
NODE_EXTRA_CA_CERTS is set for the GUI process (verified via a LaunchAgent + ps eww on the ZCode PID), the file is readable, and the standalone SDK — which also uses that var — connects successfully.
- Happy to provide a tiny capture-proxy repro so a maintainer can reproduce the "0 requests from ZCode" side on any Streamable HTTP server.
Summary
A self-hosted MCP server (Streamable HTTP) connects fine through the standalone
@modelcontextprotocol/sdk(the same version ZCode bundles,^1.26.0), but ZCode reportsfetch failedfor the same server with the same token. The decisive finding: when ZCode is pointed at a capture proxy, no request reaches the proxy at all — ZCode fails before making any network call, inside the process, while the standalone SDK against the same proxy sends all requests and connects.Environment
@modelcontextprotocol/sdk@^1.26.0/mcp), returnsmcp-session-id+text/event-streamtype: "http",headers.Authorization: Bearer …Reproduce
1. The server is healthy (verified independently)
2. The standalone SDK connects (same SDK ZCode bundles)
Running
@modelcontextprotocol/sdk@^1.26.0in plain Node against the server:3. ZCode fails with no network call
I pointed the ZCode
ai-memoryserver at a capture proxy (http://127.0.0.1:3129/mcp) that forwards to the real server and logs every request, then restarted ZCode.fetch failed.So ZCode's MCP client throws
fetch failedwithout ever opening a connection to the server/proxy. This is not a network/TLS/CA/token issue (the standalone SDK rules all of that out) and not an SDK-version issue (same^1.26.0). It's specific to how ZCode invokes the SDK inside the Electron process.ZCode log
{ "event": "mcp.server.failed", "level": "warn", "message": "MCP server connection failed", "context": { "error": "fetch failed", "mcpServerName": "<name>", "transport": "http" }, "durationMs": 90 }Failures are consistently 44–185 ms — far below any network/TLS timeout and below the 30 s tool timeout — consistent with a synchronous, pre-network failure rather than a network timeout.
Two problems
fetch failedbefore sending any request, while the same SDK in plain Node works against the same server. Something in how ZCode builds or dispatches the Streamable HTTP request fails (or is aborted) before reaching the network stack.fetch failed— theerror.cause/ code from the SDK (which undici nests) is never serialized. Whether the root cause is a header issue, a URL parsing issue, an abort, or something else, there is no way to tell from ZCode's output. At minimum, surfacingerror.causewould make cases like this self-diagnosing.Expected
error.cause, error code), not justfetch failed.Notes
NODE_EXTRA_CA_CERTSis set for the GUI process (verified via a LaunchAgent +ps ewwon the ZCode PID), the file is readable, and the standalone SDK — which also uses that var — connects successfully.