Chasing an MCP Timeout: SQLcl, Swap, and a Server Open to My Network

A yellow "Couldn't start for Cowork and Code sessions" pop-up sent me through a night of log reading. The real culprit was memory pressure, not SQLcl. Along the way I found Unsloth Studio listening on my whole network, and locked it down.

How it started

Late Saturday night, a yellow pop-up appeared in the Claude desktop app on my Debian machine: “MCP sqlcl: Couldn’t start for Cowork and Code sessions. Error: Request timed out.” SQLcl is Oracle’s command-line SQL client. I run it as an MCP server, so Claude can query my lab databases.

The error had been showing up for weeks, on and off. This time I pulled the full log and worked through it one command at a time. The chain ended somewhere I didn’t expect: a web app on my machine open to my entire local network.

This post walks through each step: what I ran, what the output meant, and what I ruled out.

Step 1: Reading the MCP log

The log covered Aug 21 to Oct 11, 2026, and it mixed three kinds of lines: a normal handshake, harmless warnings, and one real error.

A healthy startup looks like this:

  1. Initializing server…: the app launches SQLcl.
  2. MCP SERVER STARTUP … started successfully: SQLcl’s Java process is up.
  3. method="initialize": the app says hello and names its protocol version.
  4. Message from server: id=0 result: SQLcl answers.
  5. tools/list, prompts/list, resources/list: the app asks what SQLcl can do. Answers here mean the connection works.

Warnings that turned out to be harmless:

WarningWhat it means
Client requested unsupported protocol version: 2025-11-25, so the server will suggest the 2024-11-05 versionNormal version negotiation. Both sides settle on the older version and carry on.
Roots list changed notification, but no consumers providedThe app shares a folder list; SQLcl doesn’t use that feature.
No handler registered for notification method: notifications/cancelledThe leftover from a timeout, not a separate problem.

The real error: Couldn't start this server for Cowork and Code sessions … Request timed out. The desktop app runs one copy of SQLcl for regular chat and a shared copy for agent sessions. The shared copy was the one failing, while chat kept working.

The timestamps explained it. The [sqlcl] lines are in UTC, while SQLcl’s banner prints Hawaii time (UTC−10):

Aug 21 (good)Aug 26 (bad)
App launches SQLcl15:41:25 UTC20:04:15 UTC
SQLcl banner appears15:41:27 UTC20:06:00 UTC
Time to startabout 2 secondsabout 105 seconds

The app gives up after roughly 50–60 seconds. On bad days, SQLcl simply started too slowly. The log also showed two or three copies starting in the same second, competing for the same CPU and memory.

Step 2: Ruling out the usual suspects

Three checks cleared Java and SQLcl themselves.

Is there more than one Java?

which -a java

It listed /usr/bin/java and /bin/java. Running ls -ld /bin showed /bin -> usr/bin: Debian’s merged-/usr layout makes /bin a symbolic link, so both entries are the same file. One Java.

A side note on that ls output: the permissions ended in a dot (lrwxrwxrwx.). That dot marks an SELinux security label, likely left over from my recent SELinux experiment before I rolled back to AppArmor.

Which Java, and which SQLcl?

CommandResultVerdict
java -versionAmazon Corretto 26.0.2.1, with Class Data Sharing onNew enough (SQLcl needs 17+), but a non-LTS release; worth suspecting if SQLcl ever acts strangely
sql -versionSQLcl 26.2.1Current; Oracle versions SQLcl by year and quarter

Is SQLcl slow by nature?

time (echo exit | ~/oracle-tools/sqlcl/bin/sql /nolog)

This starts SQLcl without logging into any database, feeds it exit, and times it. Result: real 0m3.433s. user time (4.0 s) was higher than real, because Java spreads startup work across several CPU cores.

That was a warm start: SQLcl had run minutes earlier, so its files were still cached in RAM. The failures in the log looked like cold starts, after boot or wake, or under load.

Step 3: The real culprit, memory pressure

free -h showed 3.8 GiB sitting in swap on a 15 GiB machine. That means RAM had run out at some point.

Rowusedfreebuff/cacheavailable
Mem (15 GiB)7.8 GiB989 MiB7.5 GiB7.8 GiB
Swap (11 GiB)3.8 GiB8.1 GiB

Low free is normal: Linux puts spare RAM to work as file cache. The available column is the one that matters, and it looked fine at that moment. The swap number told the real story. Swapped data has to come back from disk when needed, and when SQLcl starts in the middle of that, three seconds stretches to a hundred.

Next, I listed the biggest memory users:

ps aux --sort=-%mem | head -n 15
ProgramApprox. RAMNote
Wazuh indexer (Java / OpenSearch)1.4 GBHeap fixed at 1 GB and claimed up front
Microsoft Edge (several processes)1.2 GB+Each tab is its own process
Zoom (3 processes)1.2 GBStill using CPU hours after joining
Claude desktop (3 processes)1.1 GBElectron app
Cinnamon desktop450 MBNeeded
JetBrains Toolbox410 MBJust the launcher, minimized
OpenClaw gateway (Node)320 MBBackground service

SQLcl didn’t even make the top 15. No single program was the problem: it was a dozen mid-sized ones, plus two or three SQLcl copies starting at the same moment.

Wazuh’s startup flags explain its size. -Xms1024m -Xmx1024m fixes the Java heap at 1 GB, and -XX:+AlwaysPreTouch makes Java claim that whole gigabyte at startup. That’s a sensible choice on a dedicated server, but costly on a shared desktop.

The fix is unglamorous: quit Zoom after meetings, close idle tabs, quit JetBrains Toolbox when not coding, and stop Wazuh when the SIEM lab isn’t in use. When the pop-up appears, restarting the Claude app usually connects on the second try, because the files are then cached.

Side quest: Who can reach OpenClaw?

The ps list showed my OpenClaw gateway listening on port 18789. An AI-agent gateway is not something I want reachable from other devices, so I checked:

ss -tlnp | grep 18789
FlagMeaning
-tTCP only
-lListening sockets only
-nNumbers, not names
-pShow the owning process

Both lines showed 127.0.0.1:18789 and [::1]:18789, loopback on IPv4 and IPv6. Only my own machine can connect. The confusing 0.0.0.0:* in the output sits in the peer column, not the local one, so it doesn’t widen anything.

One caveat remains: loopback still lets any program on the machine connect, including a malicious web page in a browser. That’s why a gateway like this should also require an auth token.

Step 4: Updating Unsloth Studio

unsloth studio update moved the Unsloth package from 2026.4.4 to 2026.10.3 and installed all 11 dependencies. The llama.cpp part hit a snag, but the installer handled it well.

The 404. The installer picked a prebuilt llama.cpp release tagged b11541-mix-2500203 from Unsloth’s own fork. It then tried to download matching source code from the original ggml-org/llama.cpp project using the same tag, and GitHub returned HTTP Error 404: Not Found. The older b11539-mix-… release failed the same way. The -mix- tag appears to exist only in Unsloth’s fork, so this looks like an installer bug on their side, not a network problem on mine.

The safe rollback. The installer downloaded into a separate staging folder, planned to validate the new build against a tiny 260K-parameter test model, and restored my existing install when that failed. Then it fell back to compiling llama.cpp from source.

The source build. It finished with building (CUDA, sm_61), llama.cpp built, and llama-quantize built. sm_61 means it was compiled for my GPU’s compute capability 6.1. The card is a GeForce GT 1030 with 2 GB of video memory, so most model work will still run on the CPU.

An open question. Running the update a second time printed unsloth 2026.4.4 -> 2026.10.3 … updating again, as if the first update hadn’t stuck. Possible causes are two copies of Unsloth (Anaconda’s base environment vs. Studio’s own environment) or a faulty version check. The next check:

~/.unsloth/studio/unsloth_studio/bin/python -m pip show unsloth

Step 5: Unsloth Studio was open to my network

The installer’s suggested launch command was unsloth studio -H 0.0.0.0 -p 8888. 0.0.0.0 means “listen on every network interface.” My running Studio had been started that way.

Its banner made things look worse. Under “From another device on your network / to share,” it printed a global IPv6 address. IPv6 has no NAT, so a global address can be reachable from the internet unless a firewall blocks it. Studio does require a password: the log showed one failed login (401) and then a successful one (200). But a password shouldn’t be the only barrier.

The timestamps also showed this server had started before either update, so it was still running the old code.

Checking instead of trusting the banner:

ss -tlnp | grep 8888

The output showed only 0.0.0.0:8888, with no [::]:8888 line. Studio was listening on IPv4 only, so the advertised IPv6 link led nowhere: the banner was misleading. It was still reachable by any device on my local network.

The fix, one command at a time:

StepCommandResult
1kill 251505No output, which means success. kill sends SIGTERM, a polite shutdown request.
2ss -tlnp | grep 8888No output: port 8888 is free.
3unsloth studio -H 127.0.0.1 -p 8888Uvicorn running on http://127.0.0.1:8888, new PID 296026, and the “share” section gone from the banner
4ss -tlnp | grep 8888 (second terminal)127.0.0.1:8888, pid 296026

Studio is now reachable only from my own machine, and it’s running the updated code. From now on, I launch it with -H 127.0.0.1 every time.

Lessons learned

  1. Read timestamps across time zones. Comparing UTC log lines with local-time banners turned “it times out sometimes” into “it takes 105 seconds instead of 2.”
  2. Rule out the cheap suspects first. Three short commands cleared Java and SQLcl, so I stopped guessing about versions.
  3. Swap usage is evidence of past trouble. available memory looked fine, but 3.8 GiB in swap showed RAM had run out before.
  4. Verify with ss; don’t trust the banner. Studio advertised an IPv6 share link it wasn’t serving. The socket table told the truth.
  5. Default to 127.0.0.1. Any dev tool suggesting 0.0.0.0 gets changed to loopback unless I need another device to reach it.

Study connections

TopicAWS DVA-C02Black Hat Python
Slow JVM start vs. timeoutLambda cold starts; SnapStart for JavaSocket settimeout() expiring before a reply
Cold vs. warm cacheCold vs. warm Lambda execution environments—
Amazon CorrettoThe Java behind Lambda’s Java runtimes and Amazon Linux—
Wazuh indexer (OpenSearch)Amazon OpenSearch Service for log analytics—
/api/health endpointALB target group health checks—
Hourly token refreshCognito access tokens (1 hour by default) and refresh tokens—
Staged update with rollbackBlue/green deployments in CodeDeploy and Elastic Beanstalk—
HTTP Error 404 from urllib—404 vs. 200 is how a web brute-forcer maps paths
Login 401 then 200—What a password brute-forcer watches for
Listening ports (ss)—Port scanning and the chapter 2 TCP client
kill and SIGTERM—Catching signals with Python’s signal module

Still open

  • Confirm the Unsloth version inside Studio’s environment with pip show unsloth
  • Check the host firewall, so I know what protects me if an app binds to 0.0.0.0 or IPv6 again
  • Decide whether to stop Wazuh and disable its autostart outside lab sessions