feat(#91): sidecar config detection + precedence + verified paths (P0) #97

Closed
the_og wants to merge 2 commits from feat/91 into feat/90
Owner

What (P0 — correctness)

ssh-mcp v2 reads a TOML sidecar and only falls back to CLI args when that file is absent. So BCC's carefully-managed --host/--user args can be completely inert while the real config lives in a file BCC never looks at. This PR adds read-only truth-telling for that trap (no sidecar writing — that's a deliberate follow-up).

Core (pure bcc_core, fully injectable — CI never touches the real filesystem)

  • sidecar_path(spec) — the VERIFIED per-platform TOML location, derived from the package source, NOT the README (which is wrong on macOS/Windows):
    • macOS → ~/Library/Application Support/ssh-mcp/config.toml
    • Windows → %APPDATA%\ssh-mcp\config.toml
    • else → ${XDG_CONFIG_HOME:-~/.config}/ssh-mcp/config.toml
      sidecar_doc_path() returns the README path for comparison.
  • sidecar_status() — resolves exists / has_managed_args / args_inert / wrong_path. platform / environ / home / exists are all injectable.
  • sidecar_warnings() — mirrors removed_flag_warnings' shape. Reports three things:
    1. Precedence — "these arguments are inert; the server reads <real path>, edit that instead" (only when the sidecar exists and managed args are present).
    2. Wrong-path — a TOML sitting at the README path that the server never actually reads (macOS/Windows only; on Linux the paths coincide, so no false alarm).
    3. #11 credential scoping — an unprefixed SSH_MCP_PASSWORD shared across 2+ profiles in a multi-profile sidecar.

GUI (minimal, advisory)

A read-only warning label in the stdio editor, mirroring the existing removed-flag label. No fix button — editing the sidecar is a separate, deliberate action. Uses the real platform/env/filesystem so it reflects the user's actual machine. Smoke-tested headlessly with QT_QPA_PLATFORM=offscreen.

⚠️ Heuristic flagged honestly

count_toml_profiles() counts TOML table/array-of-table headers as a 3.10-safe proxy for "how many profiles are defined" (no tomllib on the py3.10 baseline). The exact ssh-mcp multi-profile schema wasn't verifiable from here, so the #11 warning is conservative and documented as best-effort. It only ever advises — it never edits or blocks.

Testing

ruff + format --check clean; pytest green. Added fixture-driven tests: verified paths per platform, args-inert precedence, args-live when absent, wrong-path on macOS + no-false-alarm on Linux, profile counting, and the #11 credential-scoping warning.

Closes #91. Part of epic #94.


Stacked PR — base is feat/90 (this depends on the ServerSpec spine). Review/merge #90 first, then this. Sibling of #92; #93 builds on this.
🤖 Generated with Claude Code

## What (P0 — correctness) `ssh-mcp` v2 reads a TOML **sidecar** and only falls back to CLI args when that file is **absent**. So BCC's carefully-managed `--host`/`--user` args can be completely **inert** while the real config lives in a file BCC never looks at. This PR adds **read-only truth-telling** for that trap (no sidecar writing — that's a deliberate follow-up). ### Core (pure `bcc_core`, fully injectable — CI never touches the real filesystem) - **`sidecar_path(spec)`** — the **VERIFIED** per-platform TOML location, derived from the package source, **NOT the README** (which is wrong on macOS/Windows): - macOS → `~/Library/Application Support/ssh-mcp/config.toml` - Windows → `%APPDATA%\ssh-mcp\config.toml` - else → `${XDG_CONFIG_HOME:-~/.config}/ssh-mcp/config.toml` `sidecar_doc_path()` returns the README path for comparison. - **`sidecar_status()`** — resolves `exists` / `has_managed_args` / `args_inert` / `wrong_path`. `platform` / `environ` / `home` / `exists` are all injectable. - **`sidecar_warnings()`** — mirrors `removed_flag_warnings`' shape. Reports three things: 1. **Precedence** — *"these arguments are inert; the server reads `<real path>`, edit that instead"* (only when the sidecar exists **and** managed args are present). 2. **Wrong-path** — a TOML sitting at the README path that the server never actually reads (macOS/Windows only; on Linux the paths coincide, so no false alarm). 3. **#11 credential scoping** — an unprefixed `SSH_MCP_PASSWORD` shared across 2+ profiles in a multi-profile sidecar. ### GUI (minimal, advisory) A read-only warning label in the stdio editor, mirroring the existing removed-flag label. **No fix button** — editing the sidecar is a separate, deliberate action. Uses the real platform/env/filesystem so it reflects the user's actual machine. Smoke-tested headlessly with `QT_QPA_PLATFORM=offscreen`. ### ⚠️ Heuristic flagged honestly `count_toml_profiles()` counts TOML table/array-of-table headers as a 3.10-safe proxy for "how many profiles are defined" (no `tomllib` on the py3.10 baseline). The exact ssh-mcp multi-profile schema wasn't verifiable from here, so the #11 warning is conservative and documented as best-effort. It only ever *advises* — it never edits or blocks. ### Testing `ruff` + `format --check` clean; `pytest` green. Added fixture-driven tests: verified paths per platform, args-inert precedence, args-live when absent, wrong-path on macOS + no-false-alarm on Linux, profile counting, and the #11 credential-scoping warning. Closes #91. Part of epic #94. --- **Stacked PR — base is `feat/90`** (this depends on the ServerSpec spine). Review/merge **#90 first**, then this. Sibling of #92; #93 builds on this. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
the_og added 1 commit 2026-08-12 03:00:12 -04:00
feat(#91): sidecar config detection + precedence + verified paths
CI / Tests (py3.12 / windows-latest) (pull_request) Failing after 23s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 46s
CI / Catalog signature (pull_request) Successful in 8s
CI / Lint (ruff) (pull_request) Successful in 36s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 15s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 16s
8c51c25211
ssh-mcp v2 reads a TOML sidecar and only falls back to CLI args when that file
is ABSENT — so BCC's managed --host/--user args can be completely inert while
the real config lives in a file BCC never looks at. This adds read-only
truth-telling for that (no sidecar writing).

Core (pure, fully injectable platform/environ/home/exists/read for testing):
- sidecar_path(spec): VERIFIED per-platform TOML location from the package source,
  NOT the README (macOS → ~/Library/Application Support/ssh-mcp, Windows → %APPDATA%,
  else → ${XDG_CONFIG_HOME:-~/.config}). sidecar_doc_path() is the README path.
- sidecar_status(): resolves exists / has_managed_args / args_inert / wrong_path.
- sidecar_warnings(): mirrors removed_flag_warnings' shape. Reports:
  * precedence — "these arguments are inert; the server reads <real path>"
  * wrong-path — a TOML at the README path the server never actually reads
  * #11 credential scoping — an unprefixed SSH_MCP_PASSWORD shared across 2+
    profiles in a multi-profile sidecar (count_toml_profiles is a documented
    3.10-safe heuristic; unscoped_credential_warning gates on it).

GUI: a read-only advisory label in the stdio editor (mirrors the removed-flag
label; no fix button — editing the sidecar is a separate deliberate action).
Uses the real platform/env/filesystem so it reflects this machine.

pytest green, ruff + format clean. Closes #91. Part of epic #94.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the_og added 1 commit 2026-08-12 03:07:49 -04:00
test(#91): assert sidecar paths via as_posix() for the Windows CI runner
CI / Lint (ruff) (pull_request) Successful in 9s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 22s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 13s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 15s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 13s
CI / Catalog signature (pull_request) Successful in 8s
2e5d0351b4
The CI matrix includes a windows-latest job where str(Path("/Users/…")) renders
with backslashes, so the platform-parameterised sidecar-path assertions failed
there (macOS/Linux passed). Compare .as_posix() instead — separator-normalised
and portable — and note why. Pure test fix; no production-code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the_og closed this pull request 2026-08-12 23:53:46 -04:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.