Skip to content

feat(mcp_registry): #201 PR 3 of 5. Filesystem install/uninstall + LockBackend lease + serializer + CLI#332

Merged
dep0we merged 7 commits into
mainfrom
feat-mcp-server-registry-backend-pr3
Jun 4, 2026
Merged

feat(mcp_registry): #201 PR 3 of 5. Filesystem install/uninstall + LockBackend lease + serializer + CLI#332
dep0we merged 7 commits into
mainfrom
feat-mcp-server-registry-backend-pr3

Conversation

@dep0we
Copy link
Copy Markdown
Owner

@dep0we dep0we commented Jun 4, 2026

Summary

PR 3 of 5 of the MCPServerRegistryBackend arc (#201). Ships the filesystem write paths + the mcp.md serializer that was missing from the codebase + the LockBackend lease around the read-modify-write critical section + CLI install/uninstall subcommands. After this PR, operators on every backend have atomic-agents mcp-registry install as the canonical install surface; filesystem write paths are proven before PR 4 ships the HTTP backend.

Architecture (7 substantive commits):

  • spec amendments (3a3a23e): six fixes to spec/36 closing prep-pass findings before implementer dispatch (constructor signature + install_lock_timeout kwarg, default lock factory routes through get_default_lock_backend, new §Install/uninstall semantics + §LockBackend integration subsections, MUST 9 contract updates, Capabilities label flip)
  • Stream 1 implementer (cdaea26): atomic_agents/mcp.py gains render_mcp_md_section + render_mcp_md_full serializers (4 round-trip tests); atomic_agents/mcp_registry/filesystem.py lands install() + uninstall() with context-manager lock idiom + dual-probe collision detection + check_lock_lost discipline; capability flags flip True; MCPRegistryError rebased to inherit from AtomicAgentsError
  • Stream 2 implementer (9926ea7): atomic_agents/cli.py adds install + uninstall subparsers + handlers with WARN-on-literal-env per decision 3 + H2 injection refusal + exception handler updates + lazy import block updates; tests/test_mcp_server_registry_filesystem_install.py NEW (28 tests); conformance suite gains MUST 3 True-branch tightening + MUST 9 atomicity + MUST 9 idempotency + MUST 10 post-install consistency tests
  • doctor test refresh (9ffc42a): capability snapshot assertion updated for the PR 3 flag flip
  • review army fixes (c4ffd14): five cross-model triple-confirmed P1 findings closed inline (H2 injection refusal across all spec fields, cleanup_stale_tempfiles MUST 2 violation, BackendNotRegistered escape, check_lock_lost broaden, lock timeout test) plus seven auto-fix items (late imports, stale docstrings, test assertion gaps, CLI H2 asymmetry)
  • CHANGELOG + CLAUDE.md (a86dc91): PR 3 entry + test count refresh
  • doc sync (d9a08c3): README + CLAUDE.md drift fixed

Test Coverage

Test delta: +33 net new (3,199 collected before PR 3, 3,232 after; 3,176 passing + 56 skipped + 0 failures + 0 regressions across the full suite).

  • tests/test_mcp.py (+4): serializer round-trip basic / env-not-resolved / strips-description-newlines / full-file
  • tests/test_mcp_server_registry_filesystem_install.py NEW (28 tests): install happy path + cold-start mcp.md creation + collision raising MCPServerAlreadyInstalled + path-traversal name raising ValueError + empty-command rejection + $VAR env round-trip + install/load round-trip + uninstall present/absent/double-call/install-uninstall-install cycle + concurrent same-name exactly-one-wins + concurrent different-names all-win + lock-timeout-zero-under-contention + CLI no-env-echo + CLI WARN on literal env + CLI refuses H2 in description + _parse_env_flag / _parse_args_flag unit tests
  • tests/test_mcp_server_registry_conformance.py (+3 new + 2 tightened + 1 docstring fix): MUST 9 install atomicity + MUST 9 uninstall idempotency + MUST 10 post-install consistency added; MUST 3 install/uninstall True-branches tightened from except Exception: pass to typed assertions; line-714 docstring corrected to reflect the custom single-read-parse implementation
  • tests/test_mcp_server_registry_filesystem_backend.py (-21): placeholder @pytest.mark.skip("PR 3") stubs removed
  • tests/test_mcp_server_registry_doctor.py (+6/-5): capability snapshot assertions flipped from PR 1/2 False baseline to PR 3+ True baseline

Plan Completion

14/14 DONE per /ship Step 8 plan-completion audit against ~/.gstack/projects/dep0we-atomic-agents-stack/dep0we-main-pr3-prep-notes-20260604.md. All 6 spec amendments, 8 code items, and 4 test items delivered with file:line citations. Cross-stream handoffs (serializer dependency between Stream 1 and Stream 2 tests) merged cleanly via worktree isolation.

Pre-Landing Review

7 issues found (1 critical, 6 informational) by the /ship checklist pass. All triple-confirmed CRITICAL/P1 items closed inline in commit c4ffd14:

  • H2 injection via newline in spec.command / spec.args / spec.env keys + values (Claude Adversarial CRITICAL FIXABLE + Codex P1 + Pre-Landing). An API caller could construct MCPServerSpec(command="npx\n## evil\ncommand: sh", ...) and install() would write multi-section content the parser interpreted as MULTIPLE H2 sections, bypassing collision detection + name validation with no audit record. render_mcp_md_section now raises ValueError on newlines in any of these fields; CLI defense-in-depth refuses newlines at parse time with operator-readable errors naming the offending flag. Description H2 guard regex aligned (re.match(r'^##\s', line)) to catch the ##\t tab-separated case.
  • cleanup_stale_tempfiles in init violated MUST 2 (Pre-Landing CRITICAL + Codex P1 + Performance + Claude Adversarial — quadruple-confirmed). Constructor was recursively deleting .*.tmp anywhere under agent_root via rglob, changing filesystem state during construction (violates side-effect-free MUST) and could delete unrelated user/application tempfiles, including from read-only commands like list. New cleanup_stale_tempfiles_for_file(target) helper in _io.py scoped to a single target file's siblings (glob, not rglob); install/uninstall call it inside the lock before reading mcp.md. Constructor is side-effect-free again.
  • BackendNotRegistered escape from locks module (Codex P2 + Claude Adversarial). Operator typos in ATOMIC_AGENTS_LOCK_BACKEND raised atomic_agents.exceptions.BackendNotRegistered (NOT subclass of MCPRegistryError) which escaped the CLI handler as a raw Python traceback. _resolve_lock_backend now wraps get_default_lock_backend in try/except and re-raises as MCPRegistryUnavailable so the CLI's existing catch-all handles it cleanly.
  • check_lock_lost non-LockLost exceptions (Codex P3 + Claude Adversarial). The try/except LockLost pattern only caught LockLost; ImportError from broken redis dep, AttributeError from malformed handle.backend_state, etc. escaped raw. Broadened to catch any exception and translate to MCPRegistryUnavailable.
  • Lock timeout test missing (Testing specialist CRITICAL + Pre-Landing + Maintainability). Module docstring promised category 7 but no implementation existed. test_install_lock_timeout_zero_under_contention now exercises the spec/36 MUST 9 LockBusy → MCPRegistryUnavailable contract.

Auto-fix cluster also applied: late imports (render_mcp_md_full + check_lock_lost) moved to top-level; stale docstrings in filesystem.py + backend.py updated to PR 3+ baseline; test assertion gaps closed; CLI H2 description guard aligned to renderer.

Specialist Review

Seven parallel Sonnet specialists (testing + maintainability + security + performance + plan-completion + checklist-pass + Claude-adversarial) plus Codex cross-model verification. Zero P0 findings for the secret-leak class (PR 1's symmetric bug class did NOT return in PR 3). Cross-stream convergence on 5 findings — strongest possible "this is real" signal.

Adversarial Review

Claude adversarial subagent + Codex codex exec (both at high reasoning). Both independently flagged the H2 injection vector (P1, Recommendation: "block before shipping") and cleanup_stale_tempfiles (P1). Both fixes applied in c4ffd14.

Eval Results

No prompt-related files changed — evals skipped per /ship Step 6 gate.

Greptile Review

Not applicable — PR didn't exist during /ship Step 10.

TODOS

No TODOS.md exists in the repository. Per /ship Step 14, the user declined to create one this cycle. Project follow-up tracking remains in GitHub Issues.

Documentation

  • CHANGELOG.md: PR 3 entry added under [Unreleased] covering filesystem install/uninstall + LockBackend lease + render_mcp_md serializers + CLI install/uninstall + spec/36 PR 3 amendments + the 5 cross-model triple-confirmed review army findings + test delta (+33).
  • CLAUDE.md: Test count refreshed from 3,199 (2026-06-03) to 3,232 (2026-06-04) at both occurrences; spec count updated from 33 docs to 35 docs (4 drafts now include spec/35 init wizard and spec/36 MCPServerRegistryBackend).
  • README.md: spec/36 progress tag updated from "PR 1 of 5" to "PR 3 of 5"; test count updated to 3,232 at both occurrences; MCPServerRegistryBackend status flipped from "planned" to "in progress (PR 3 of 5)" in the status block.
  • docs/spec/36-mcp-server-registry-backend.md: amendments landed at commit 3a3a23e BEFORE implementer dispatch (constructor + install_lock_timeout kwarg, default lock factory routing through get_default_lock_backend, new §Install/uninstall semantics + §LockBackend integration subsections, MUST 9 contract updates with context-manager idiom + LockBusy mapping + check_lock_lost discipline + no-fast-path rule + mtime preservation note, Capabilities label flipped from PR 1/2 to PR 3+).

Test plan

  • All 3,176 tests pass (3,232 collected, 56 skipped, 0 failures, 0 regressions)
  • uv run ruff check clean on all PR 3 files
  • uv run ruff format --check clean on all PR 3 files
  • Spec/36 amendments cross-verified against implementation by post-impl plan-completion audit (14/14 DONE)
  • Five cross-model triple-confirmed review army findings closed inline
  • Pre-impl prep pass (5 streams) + post-impl review army (7 subagents) + Codex verification all completed

🤖 Generated with Claude Code

Dan Powers and others added 7 commits June 4, 2026 08:27
Spec/36 PR 3 prep landed 6 amendments closing prep-pass findings before
implementer dispatch:

1. FilesystemMCPServerRegistryBackend constructor: default lock_backend
   factory routes through get_default_lock_backend(agent_root) so multi-host
   operators pinning ATOMIC_AGENTS_LOCK_BACKEND=redis get the right backend
   automatically (Stream A finding A-2, P0).

2. Constructor signature adds install_lock_timeout: float = 30.0 kwarg
   per the spec/21 apply_staging_lock_timeout precedent (Stream A finding
   A-7, P1).

3. New "Install / uninstall semantics" subsection documents the 7-step
   critical section with dual-probe collision detection (Stream B finding
   B-4, P1), check_lock_lost discipline (Stream A finding A-6, P1), full
   crash safety analysis, and the renderer round-trip property the new
   mcp.py serializer must satisfy (Stream B+C convergent finding B-1, P0
   blocker).

4. New "LockBackend integration" subsection documents the context-manager
   idiom, install_lock_timeout knob, LockBusy -> MCPRegistryUnavailable
   translation (Stream A+B+D convergent), custom-lock-backend operator
   surface failure mode (Stream A finding A-8, P2), and non-reentrant
   default.

5. MUST 9 contract updated to require the context-manager idiom (closing
   Stream A finding A-3, P0: handle.release() does not exist on LockHandle
   per spec/21), explicit LockBusy mapping, check_lock_lost discipline,
   and no-fast-path discipline for absent-name uninstall (Stream A finding
   A-9, P2).

6. Capabilities label flipped from "PR 1/2" to "PR 3+" reflecting the
   supports_install / supports_uninstall flag flip landing in this PR.

These amendments are spec-only at this commit. Implementation lands in
the following commits via parallel Sonnet implementer streams. Per the
PR 1+2 cadence: spec amendments commit first, implementation follows,
the /ship review army then verifies spec-code coherence end-to-end.

Cross-stream prep findings: 58 total (11 P0, 17 P1, 21 P2, 9 P3). Five
critical convergences confirmed by 2+ streams independently: the mcp.md
serializer gap (B+C), LockBusy -> MCPRegistryUnavailable mapping (A+B+D),
lock try/finally discipline for all exit paths (A+B+C), default lock
factory instantiation (A+B), CLI exception handler MCPServerAlreadyInstalled
gap (D+A).

Refs #201.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…alizer + capability flip + MCPRegistryError base

Lands the filesystem write paths per spec/36 PR 3 amendments at 3a3a23e:

- atomic_agents/mcp.py: render_mcp_md_section + render_mcp_md_full
  serializers. Round-trip property pinned with 4 new tests; $VAR refs
  written verbatim (never resolved) per Decision 7. Also removes the
  pre-existing unused safe_resolve_under import in validate_mcp_server_args
  (was masked by the re unused-import which serializers now fix).
- atomic_agents/mcp_registry/filesystem.py: install + uninstall via
  context-manager lock idiom + dual-probe collision detection +
  check_lock_lost discipline + cleanup_stale_tempfiles in __init__.
  Constructor adds install_lock_timeout kwarg + lazy lock-backend
  resolver routing through get_default_lock_backend so
  ATOMIC_AGENTS_LOCK_BACKEND=redis works automatically. Capability
  flags flip supports_install=True, supports_uninstall=True (MUST 3).
- atomic_agents/mcp_registry/backend.py: MCPRegistryError now inherits
  from AtomicAgentsError so framework-wide catch-alls see registry
  failures. All subclasses inherit transitively.

Closes prep-pass findings: A-1/A-2/A-3/A-4/A-5/A-6/A-7/A-8 (LockBackend
integration), B-1/B-2/B-3/B-4/B-5/B-7/B-9/B-10 (install atomicity +
serializer), C-Serializer/C4/C5/C1/C2/C9 (uninstall idempotency),
E12 (MCPRegistryError base class).

Stream 2 ships the CLI + tests + conformance updates in parallel.

Refs #201.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ilesystem install tests + conformance MUST 9 / 10 coverage

Lands the CLI surface + behavior tests for the PR 3 filesystem write
paths per spec/36 amendments at 3a3a23e:

- atomic_agents/cli.py: install + uninstall subparsers under mcp-registry
  with --command / --args / --env / --description / --transport flags;
  WARN on stderr for --env values that don't start with $ (per decision
  3); refuse --description with H2 lines (defense-in-depth); success
  output prints ONLY the returned Ref.name (never echo env / command /
  args per PR 1 P0 secret-leak class). Lazy import block updated to
  include MCPServerAlreadyInstalled + MCPRegistryError. Exception
  handler adds MCPServerAlreadyInstalled + MCPRegistryError base-class
  backstop. Top-level description + module docstring updated to remove
  "PR 3 deferred" language. Pre-existing F541 f-string lint fixed in
  _corpus_show.
- tests/test_mcp_server_registry_filesystem_install.py NEW: 27 tests
  covering install/uninstall happy paths + collision + cold-start +
  path-traversal + idempotency + cycle + concurrent install +
  CLI WARN/refuse/no-leak discipline + _parse_env_flag / _parse_args_flag
  unit tests. 18 fail until Stream 1 merges (expected); 9 pass now
  (CLI parser + H2-guard tests that don't reach the unimplemented backend).
- tests/test_mcp_server_registry_conformance.py: tightened MUST 3
  True-branch for install (assert isinstance returns MCPServerRef, not
  broad except Exception pass); tightened MUST 3 True-branch for
  uninstall (assert result is None); added MUST 9 install atomicity +
  uninstall idempotency tests guarded on capability flags so HTTP
  backend at PR 4 skips automatically; added MUST 10 post-install
  consistency test; fixed docstring lie at line 714 (filesystem uses
  custom single-read-parse, NOT _default_load_all); removed unused
  os + ValidationResult imports.
- tests/test_mcp_server_registry_filesystem_backend.py: removed obsolete
  @pytest.mark.skip placeholders for install/uninstall (real tests now
  in test_mcp_server_registry_filesystem_install.py).

Closes prep-pass findings: D-F1/D-F2/D-F3/D-F4/D-F5/D-F9/D-F10
(CLI surface), E2/E3/E4/E5/E6/E10 (conformance), B-6 (CLI literal env
contract), B-9 (test scaffold for concurrent install),
C10/C11 (uninstall conformance tests).

Stream 1 ships the serializer + filesystem.install/uninstall +
MCPRegistryError base class fix in parallel.

Refs #201.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR 3 of #201 flipped FilesystemMCPServerRegistryBackend's static
supports_install + supports_uninstall from False to True (per spec/36
Decision 5 capability evolution table + MUST 3 honesty: methods now
work, static flag flips True).

The doctor capability snapshot test was pinned to the PR 2 baseline
(install/uninstall False) and started failing on the PR 3 capability
flip. Update the assertions + comment to reflect PR 3+ baseline.
Pattern: the same assertion will flip again at PR 4/5 when HTTP backend
joins the parametrization (HTTP backend has different static defaults
per Decision 5 table).

Refs #201.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…fix cluster)

Apply 12 fixes from the /ship review army (7 parallel Sonnet specialists +
Codex cross-model verification). Five findings were triple-confirmed by
multiple independent reviewers.

P1 cross-model triple-confirmed:

- H2 injection refusal in render_mcp_md_section (mcp.py): newlines in
  spec.command, any spec.args item, any spec.env key, or any spec.env
  value now raise ValueError before rendering. Defense-in-depth at CLI
  (cli.py) refuses newlines at parse time with operator-readable
  messages naming the offending flag. The phantom-section-injection
  vector that bypassed collision detection + name validation is closed.
  Also aligned the description H2 guard to re.match(r'^##\s', line) so
  tab-separated ##\t<name> is caught (was bypass-able via startswith).

- cleanup_stale_tempfiles moved out of __init__ (MUST 2 violation).
  Constructor is side-effect-free again. New cleanup_stale_tempfiles_for_file
  in _io.py is scoped to a single target file's siblings (glob, not rglob);
  install/uninstall call it inside the lock before reading mcp.md.

- BackendNotRegistered escape from locks module fixed.
  _resolve_lock_backend wraps get_default_lock_backend in try/except and
  re-raises as MCPRegistryUnavailable so the CLI's existing MCPRegistryError
  catch-all handles it cleanly. Operator typos in ATOMIC_AGENTS_LOCK_BACKEND
  now produce clean errors instead of raw Python tracebacks.

- check_lock_lost broaden except clause: non-LockLost exceptions
  (ImportError from broken redis dep, AttributeError from malformed
  backend_state, etc.) now translate to MCPRegistryUnavailable instead
  of escaping raw.

- Lock timeout test added: test_install_lock_timeout_zero_under_contention
  exercises the spec/36 MUST 9 LockBusy -> MCPRegistryUnavailable contract.

Auto-fix cluster:

- Late imports (render_mcp_md_full + check_lock_lost) moved to top-level
  imports for visibility + micro-perf.
- Stale docstrings in filesystem.py + backend.py updated to present-tense
  PR 3+ baseline (removed PR-1 historical claims that misled readers).
- test_uninstall_idempotent_double_call now asserts result2 is None.
- test_cli_install_warns_on_literal_env_value now asserts exit_code == 0.
- test_install_empty_command_raises tightened from
  pytest.raises((ValueError, Exception)) to pytest.raises(ValueError).
- CLI H2 description guard aligned to renderer's regex (catches ##\t).

Refs #201.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…resh

CHANGELOG.md [Unreleased] gains the PR 3 of 5 entry covering:
- Filesystem install/uninstall write paths with LockBackend lease via the
  context-manager idiom
- render_mcp_md_section + render_mcp_md_full serializers with the
  round-trip property pinned
- CLI install + uninstall subcommands with WARN-on-literal-env + H2
  injection refusal at both CLI and renderer layers
- spec/36 PR 3 amendments (install_lock_timeout kwarg, default lock
  factory routing through get_default_lock_backend, new Install/uninstall
  semantics + LockBackend integration subsections, MUST 9 contract updates,
  Capabilities label flip to PR 3+)
- MCPRegistryError rebased to inherit from AtomicAgentsError
- 5 cross-model triple-confirmed review army findings closed inline
- 5-stream prep pass + 7-subagent review army cross-checked
- Test delta +33 net new (3199 to 3232, 0 regressions)

CLAUDE.md test count refreshed from 3,199 (2026-06-03 baseline) to 3,232
(2026-06-04) at both occurrences.

Refs #201.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sync README + CLAUDE.md + architecture + other docs that reference
MCPServerRegistryBackend or the test count. Fix drift where docs cite
the pre-PR-3 baseline (e.g., supports_install=False on the filesystem
backend, stale test counts, "install deferred to PR 3" language).

Refs #201.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@dep0we dep0we merged commit 11c629a into main Jun 4, 2026
5 checks passed
@dep0we dep0we deleted the feat-mcp-server-registry-backend-pr3 branch June 4, 2026 14:22
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