Server dashboard shows Docker containers, a failed Claude MCP service, dependency errors, and mobile health alerts.
A Synology MCP server in one homelab was effectively offline for 64 days without triggering an alert, because the failure happened before the service could expose a port, remain running, or register as unhealthy. The incident, detailed in a first-person report by XDA Developers, is a useful warning for anyone using Claude Desktop, uv, Docker, and self-hosted monitoring: a green container dashboard and a responding HTTP endpoint do not prove that a locally launched AI tool is usable.

The immediate culprit was an unbounded Python dependency. The server’s requirements allowed mcp>=1.28.0, so when the Model Context Protocol Python SDK published stable version 2.0.0 on July 28, 2026, the next uv environment resolution selected the major upgrade. The existing Synology integration still depended on a v1 API that version 2 removed, and the process exited before Claude Desktop could connect to it.

This was not an obscure break that package maintainers could not have anticipated. The MCP Python SDK’s own v2 release notes explicitly told projects that were not ready to migrate to retain an upper constraint such as mcp>=1.28,<2; the project also moved v1 into maintenance mode and changed APIs including the high-level server class and old import paths. The homelab failure therefore exposes a routine but consequential maintenance gap: the server’s dependency declaration described the minimum compatible version but made no promise about the maximum one.

The exited Docker container was a false lead​

The dead container the author initially found in Docker Desktop was real, but it was not the service Claude had been using. According to XDA Developers, it was an abandoned Docker attempt that started the standard-input MCP server without an interactive input stream. With stdin already closed, the process shut down almost immediately and returned exit code 0.

That behavior is exactly why exit status alone is weak evidence of availability. Exit code 0 means the program terminated without reporting an error to its parent process; it does not mean a long-running service successfully accepted a client connection, authenticated to a downstream system, or completed useful work. For a command intended to run continuously, a quick clean exit can be as operationally serious as a crash.

The live configuration was a one-line Claude Desktop launcher entry that used uv, not the unused Docker container. Each client launch built or resolved the server’s Python environment, picked up MCP 2.0.0 after July 28, and failed on the outdated v1-compatible project. The author also found that the DSM account used by the service was missing, which meant resolving the Python compatibility problem alone would not have restored access to the NAS.

That sequence is worth separating into two incidents. The dependency constraint prevented the MCP process from launching; the removed NAS account would have prevented it from doing useful work after launch. A process monitor could potentially observe the former if it watched the client launcher closely enough, but neither condition is proven fixed until the client performs an authenticated operation against Synology.

MCP 2.0 made open-ended requirements dangerous​

The reporting puts the break on July 28, 2026, the date MCP Python SDK 2.0.0 reached PyPI. That release was a major-version transition, not a routine update: pip install mcp began resolving to the 2.x line, while version 1.x moved to maintenance status for critical and security fixes.

The official migration material describes substantial changes across the SDK and protocol implementation. In particular, the v2 line adopted the July 28, 2026 MCP specification, changed the high-level server naming from FastMCP to MCPServer, and removed older APIs rather than retaining all of them as compatibility shims. A project importing an old path could fail before it has a chance to print a useful readiness message or bind any network listener.

The key operational lesson is not “never upgrade MCP.” The v2 SDK exists because the protocol changed and projects need a supported migration path. The lesson is that any local integration which must remain on an older major version needs to state that requirement in machine-readable form, preferably with an upper version bound and a lock file that is reviewed during deliberate upgrades.

For the affected server, pinning mcp<2 restored the old dependency contract. But a pin is a containment measure, not a permanent compatibility strategy. It keeps the integration on the v1 maintenance line while the upstream project catches up; it also means the operator needs a tracked task to test the newer server release against MCP 2.x before v1 becomes an unacceptable dependency.

Administrators should look beyond MCP while doing this audit. A requirement such as requests>=2, pydantic>=2, or fastapi>=0 may be perfectly reasonable, but an unconstrained major version is an explicit decision to accept a future breaking release during an ordinary install or environment rebuild. Tools such as uv make environment setup fast, but they cannot distinguish a dependency range that means “accept future major API changes” from one that was written casually.

Why Uptime Kuma and host metrics did not see it​

The author added Uptime Kuma, Beszel, and ntfy after the audit. That is a sensible small-footprint monitoring stack, but the report is right about its blind spot: a standard uptime monitor cannot validate a stdio MCP server merely by looking for a web response. A local Claude Desktop integration commonly launches a child process and exchanges JSON-RPC over standard input and output; no TCP port has to exist.

Uptime Kuma can monitor HTTP, TCP, DNS, Docker containers, and many other observable targets, while its push monitor accepts periodic success or failure updates from an external job. Those capabilities are useful, but they answer questions selected by the operator. An HTTP check can establish that a reverse proxy answered. A Docker check can establish that a container exists or is running. Neither proves that Claude Desktop can start a particular subprocess, negotiate MCP, enumerate tools, and access a NAS with its configured credentials.

Beszel fills a different role. Host metrics can reveal CPU starvation, memory pressure, disk exhaustion, or an unavailable node. They are valuable for explaining why a service failed, but a process that starts and dies in less than a second may leave no meaningful metric footprint, particularly on an otherwise idle machine.

The failure is a reminder that monitoring has layers:

  • Infrastructure monitoring establishes whether the host, storage, network, and hypervisor are alive.
  • Service monitoring establishes whether a daemon, container, URL, or port is responding.
  • Synthetic transaction monitoring establishes whether the specific user workflow still completes.

The first two layers would have helped identify related problems in this lab, including missing backups and Proxmox update failures sent to an unmonitored root mailbox. The third layer was required to detect the MCP outage. Treating those layers as substitutes creates the same false confidence that kept this failure invisible.

Test the client path, then monitor the test​

The practical fix in the XDA Developers report is the strongest part of the story: a scheduled Python check reads the same Claude Desktop MCP configuration used by the client, launches each server with its real command, completes the protocol handshake, and requests its tool list. It then sends the outcome to an Uptime Kuma push monitor.

That is a proper availability test for this class of integration because it starts at the configuration boundary that actually matters. It can catch a malformed command, a missing executable, a bad dependency resolution, an import error, an unsupported MCP protocol change, a failed initialization, and—in many cases—an invalid credential or unavailable upstream service. Asking for the tool list also proves more than a process staying alive: it confirms that the server reached the point where it can advertise a usable interface.

There are two details to preserve if readers implement the same pattern. First, run the check under the same operating-system account and with the same environment variables, working directory, and configuration files used by Claude Desktop. A test launched from an administrator shell can pass while the actual desktop client lacks permissions or cannot find a credential file.

Second, do not make the script’s final success signal unconditional. The push monitor should receive an explicit failure result when a server test fails, and it should alert if a scheduled run fails to check in within its expected interval. Otherwise, a broken scheduler, a deleted cron job, or an exception before the notification call recreates the original problem: the monitor has no definition of silence.

The author’s 30-minute schedule would have reduced a 64-day outage to a roughly 30-minute detection window, assuming notifications were routed to a phone or another actively watched destination. That is the concrete difference between collecting logs and operating a monitoring system. A log line saying “working from the local copy again” records a failure; an alert tied to an expected check-in demands a response.

For homelab operators, the immediate audit is straightforward: find every dependency file with an open major-version range, identify every service launched through a desktop client or scheduler rather than a listening port, and build one end-to-end check for each workflow that matters. The container marked “Exited (0)” may still deserve cleanup, but the more important question is whether the thing you rely on can start, authenticate, and complete its real job today.