Skip to content

fix(mcp): client must find the daemon socket where #148 moved it#258

Merged
fdaviddpt merged 2 commits into
masterfrom
fix/mcp-client-runtime-socket-path
May 29, 2026
Merged

fix(mcp): client must find the daemon socket where #148 moved it#258
fdaviddpt merged 2 commits into
masterfrom
fix/mcp-client-runtime-socket-path

Conversation

@fdaviddpt
Copy link
Copy Markdown
Contributor

Problem

The warm MCP daemon (cclsp/intelephense, phpstan-warm, phpunit-warm, rector-warm) never worked when supertool is invoked via the ./supertool symlink. Every diag: call took 60s then returned MCP server 'lsp' unavailable; the lsp-diag validator showed 15.0s (its framework timeout killing the doomed connect first).

Three compounding bugs, all masked by the same timeout:

  1. [security] MCP daemon: move UDS out of /tmp, peer-uid, O_NOFOLLOW #148 socket-path drift. [security] MCP daemon: move UDS out of /tmp, peer-uid, O_NOFOLLOW #148 moved daemon sockets from /tmp to the per-user runtime dir (_paths.socket_pid_paths). daemon/status/stop were migrated; MCPClient was not — it hardcoded /tmp/supertool-mcp-{h}.sock and polled a path the daemon never binds.
  2. abspath(__file__) under symlink. _MCP_DAEMON_SCRIPT used os.path.abspath(__file__), which doesn't follow symlinks → resolved to dvsi/presets/mcp/daemon.py (nonexistent). The client's daemon-spawn silently failed, so no daemon ever started.
  3. The same abspath base made the new from _paths import … unresolvable, which would crash the tool with ModuleNotFoundError.

Fix

  • MCPClient computes its socket path via _paths.socket_pid_paths — the single source of truth, so client and daemon can never drift again.
  • Package-relative base path uses os.path.realpath(__file__) (follows the symlink) instead of abspath, fixing both the daemon-script path and the _paths import.

Result

  • diag: against DVSI: cold 8.6s (one-time intelephense index) → warm 0.22s, with a persistent daemon. Was a 60s failure on every call.

Tests

  • TestClientDaemonPathAgreement — client socket path must equal _paths.socket_pid_paths and must not fall back to bare /tmp.
  • TestSymlinkInvocation — running supertool through a symlink must not crash on import (catches both the path drift and the abspath bug).
  • Full suite: 3070 passed, 34 skipped, 0 failed. Red→green proven for the new tests.

Co-Authored-By: Max

fdaviddpt added 2 commits May 29, 2026 12:39
The warm MCP daemon (cclsp/intelephense, phpstan-warm, etc.) never worked
when supertool is invoked via the `./supertool` symlink — every `diag:` call
took 60s then reported "MCP server 'lsp' unavailable". Three compounding bugs,
all masked by the same timeout:

1. #148 moved daemon sockets from /tmp to the per-user runtime dir
   (_paths.socket_pid_paths). daemon/status/stop migrated; MCPClient did not —
   it hardcoded /tmp/supertool-mcp-{h}.sock and polled a path the daemon never
   binds. Now it computes the path via _paths.socket_pid_paths, the single
   source of truth, so client and daemon can never drift again.

2. _MCP_DAEMON_SCRIPT used abspath(__file__), which does not follow symlinks,
   so under `./supertool` it resolved to dvsi/presets/mcp/daemon.py (nonexistent)
   and the client's daemon-spawn silently failed. Switched to realpath(__file__).

3. (3) also makes the new `from _paths import` resolvable instead of crashing
   the tool with ModuleNotFoundError.

Result: cold 8.6s (one-time intelephense index) -> warm 0.22s, persistent daemon.

Adds regression tests: client/daemon socket-path agreement, and a symlink
invocation smoke test (would have caught both the path drift and the abspath bug).

Co-Authored-By: Max <noreply>
…mutation

Review follow-up (#258). The first cut did `sys.path.insert(0, presets/mcp)` +
`from _paths import …` at module top-level — process-global, never cleaned up,
and `_paths` is a generic name that could shadow other imports. Replace with a
lazy importlib file-load under a unique module name (`_supertool_mcp_paths`):

- no sys.path mutation
- loads only on first MCPClient use, so a missing/broken _paths.py fails MCP
  ops instead of crashing the whole tool at import
- result is cached after first load

Also fix stale post-#148 docstrings in daemon.py / status.py that still claimed
sockets live in /tmp.

Co-Authored-By: Max <noreply>
@fdaviddpt fdaviddpt merged commit cea2971 into master May 29, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant