Every FreeCAD MCP tool returns Failed to get RPC status: [Errno 111] Connection refused while nc says the RPC port is open. The port is held by a Tailscale proxy listener, not by FreeCAD. The fix is to start the RPC server inside the FreeCAD GUI on the cad host. Diagnostic runbook below.

The trap in one line

On a tailscale serve / userspace-networking node, an open port proves the proxy is up — never that the backend is up.

Symptom

Every FreeCAD MCP tool call (both routes, see below) fails identically:

Failed to get RPC status: [Errno 111] Connection refused

Errno 111 is Linux ECONNREFUSED. macOS uses 61. Seeing 111 from a Mac client proves the refusal is generated on the remote Linux host, not locally — so this is not a Mac networking, DNS, or MCP-client problem. Stop debugging the laptop.

Both access routes fail together (same backend)

RouteAddress
Direct MCP server in ~/.claude.json (project /Users/levander){"type":"http","url":"https://cad.taild4189d.ts.net:8443/mcp"}
Indirect, via the aperture connectortools appear as mcp__aperture__freecad_*

Both proxy to the same FreeCAD RPC backend, so both go down together. Failing over from one to the other is not a workaround and tells you nothing new.

The nc false positive

nc -z cad.taild4189d.ts.net 9875   # reports OPEN — and is MEANINGLESS here

The port answers the TCP handshake, so nc -z calls it open. FreeCAD can still be completely absent. Per 2026-08-31-tailnet-plaintext-port-hardening, tailscaled --tun=userspace-networking unconditionally forwards inbound TCP on the node’s tailnet IP to 127.0.0.1 on the same port, independently of any tailscale serve mount. That forwarder accepts the connection first and only then tries the backend — so it reports “open” even when nothing is bound on 127.0.0.1:9875. (cad:9875 is the exact raw-forwarder surface that note flagged as left open for follow-up.)

The discriminating probe — speak a protocol, don’t just knock

Connect to 9875 and actually say something. All three of these were tried on 2026-09-02 and all three failed:

ProbeResult
XML-RPC over httpRemoteDisconnected
XML-RPC over httpsRemoteDisconnected
Raw TLS handshakeSSLEOFError: UNEXPECTED_EOF_WHILE_READING

The signature is accept-then-close with zero bytes of response, regardless of which protocol is spoken. A live XML-RPC server would answer the http probe; a live TLS endpoint would complete a handshake. Answering nothing to everything is the fingerprint of a proxy whose backend is dead.

Contrast with port 8443, which completes a real TLS 1.3 handshake. So the MCP server process itself is genuinely running and serving — only the FreeCAD RPC behind it is dead. That narrows the fault to exactly one layer.

# 1. Does the MCP front door do real TLS? (expect: yes, TLS 1.3)
openssl s_client -connect cad.taild4189d.ts.net:8443 -brief </dev/null
 
# 2. Does 9875 speak XML-RPC? (expect on failure: RemoteDisconnected)
python3 -c 'import xmlrpc.client as x; print(x.ServerProxy("http://cad.taild4189d.ts.net:9875").ping())'

For Agents

Decision rule: Errno 111 + nc open + accept-then-close on 9875 + working TLS on 8443 ⇒ FreeCAD GUI/RPC is down on the cad host. Do not re-probe, do not retry the tools, do not switch between the direct MCP server and the aperture connector. It needs a human at the CAD host.

Fix

Open FreeCAD on the cad host and start the RPC server from the freecad-mcp addon workbench — the “Start RPC Server” action. The XML-RPC server runs on the GUI thread (that is what makes designs appear live in noVNC, see 2026-08-26-freecad-cad-workstation), so no GUI ⇒ no RPC ⇒ every MCP tool fails.

cad is NOT reachable over Tailscale SSH

cad (100.120.203.1) shows online in tailscale status, but it is not in the Tailscale-SSH-enabled machine list — only telep-mainframe and telep-router are. “Online in the tailnet” and “SSH-able” are different facts; check the SSH-enabled list, not the online list.

CORRECTED 2026-09-05 — this does NOT mean the box needs a person on site. ssh 100.120.203.1 works fine. cad is not a separate machine: it is a userspace tailscaled sidecar node running on telep-mainframe, and every per-service sidecar (cad, knowledgebase, drive, chatcut, orcaslicer, bambuddy, …) lives on that one bare-metal host — so SSHing to any of their tailnet IPs lands on telep-mainframe, which is where the docker stack is anyway. (Consistent with the raw forwarder in 2026-08-31-tailnet-plaintext-port-hardening: inbound TCP on the node’s tailnet IP goes to 127.0.0.1 on the same port, so :22 hits the mainframe’s own sshd.) An entire FreeCAD recovery was performed remotely this way on 2026-09-05 — see 2026-09-05-freecad-mcp-502-dead-container-and-qt-event-loop-wedge.

Per 2026-08-26-freecad-cad-workstation the cad node is a userspace tailscaled sidecar in front of the FreeCAD docker compose stack, and the addon is configured with auto_start_rpc=true — meaning RPC is supposed to come up with the GUI on every boot. A refused RPC therefore means the GUI/container isn’t running at all, not that someone forgot to click the button. The compose-level recovery commands (docker compose ps / restart, docker logs freecad) live in that note.

The next failure mode is NOT this one

Once the container is back and get_rpc_status answers, you can land in a second, different fault: status reports healthy but every execute_code times out with GUI dispatch timed out after 90s, because a modal Document Recovery dialog owns the GUI thread. There, a restart is the wrong move. See 2026-09-02-freecad-mcp-gui-dispatch-timeout-modal-dialog. The discriminator is whether get_rpc_status itself fails (→ restart) or succeeds (→ dismiss the dialog).