Skip to content

feat(mcp): lint_url accepts real http(s) URLs via ChromiumDriver - #131

Merged
aram-devdocs merged 1 commit into
mainfrom
codex/38-feat-mcp-lint-url-real-urls
Apr 26, 2026
Merged

feat(mcp): lint_url accepts real http(s) URLs via ChromiumDriver#131
aram-devdocs merged 1 commit into
mainfrom
codex/38-feat-mcp-lint-url-real-urls

Conversation

@aram-devdocs

Copy link
Copy Markdown
Owner

Target branch

All PRs target main. Plumb has no dev branch.

  • This PR targets main

Spec

Fixes #38

Summary

  • lint_url now branches on the URL scheme: plumb-fake:// keeps the canned-snapshot path; http(s):// boots ChromiumDriver with a fixed desktop target (1280x800, DPR 1.0) and runs the rules engine on the real snapshot.
  • CdpError (Chromium-not-found / unsupported version / malformed snapshot / driver fault) surfaces as CallToolResult::error with the Display text — isError: true, deterministic message, no panic path.
  • Tool description updated to advertise both schemes; docs/src/mcp.md, the protocol-test description assertion, and the scoped plumb-mcp/AGENTS.md follow.

Crates touched

  • plumb-core
  • plumb-format
  • plumb-cdp
  • plumb-config
  • plumb-mcp
  • plumb-cli (test fixture only)
  • xtask
  • docs/
  • .agents/ or .claude/
  • .github/

System impact

  • New public API item (needs doc + # Errors section if fallible)
  • New MCP tool (needs tools/list entry + protocol test)
  • New rule (needs docs page + golden test + register_builtin entry)
  • CDP / browser surface change (needs security-auditor review)
  • Config schema change (run cargo xtask schema + commit result)
  • Dependency added / bumped (cargo-deny must still pass) — plumb-mcp now depends on plumb-cdp (layer 5 → layer 3, permitted by dependency-hierarchy.md).
  • Determinism invariant touched (see .agents/rules/determinism.md)

Architectural compliance

  • Layer discipline: plumb-core has no internal deps; unsafe only in plumb-cdp; println!/eprintln! only in plumb-cli. The new plumb-mcp -> plumb-cdp edge respects the layered hierarchy.
  • Error shape: thiserror-derived in libs; anyhow only in plumb-cli::main.
  • No new unwrap/expect/panic! in library crates.
  • No new SystemTime::now / Instant::now in plumb-core.
  • No new HashMap in observable output paths (use IndexMap).
  • Every #[allow(...)] is local and has a one-line rationale.

Test plan

  • just validate (or component checks) pass locally — cargo fmt, cargo build --workspace --all-features, cargo clippy --workspace --all-targets --all-features -- -D warnings, cargo nextest run -p plumb-mcp --all-features, cargo nextest run -p plumb-cli --all-features --test mcp_stdio all green.
  • cargo xtask pre-release passes (if rule or schema changed) — N/A.
  • just determinism-check passes (3× byte-diff clean) — to run in CI.
  • cargo deny check passes — to run in CI.
  • New/changed behavior has a test (unit, golden snapshot, or integration) — existing happy path mcp_lint_url_returns_structured_content still covers plumb-fake://. See "Reviewer notes" for the deferred protocol test.

Documentation

  • Rustdoc added for every new public item — module doc and LintUrlArgs::url updated; no new public items.
  • # Errors section on every new public fallible fn — run_stdio already documented; no new fallible public fns.
  • docs/src/ updated when user-visible behavior changed — docs/src/mcp.md reflects http(s)/fake schemes.
  • docs/src/rules/<category>-<id>.md written for new rules — N/A.
  • CHANGELOG updated if user-visible (otherwise release-please handles it) — release-please owns this.
  • Humanizer skill run on docs changes — wording kept terse; no AI-tells.

Breaking change?

  • No
  • Yes — describe migration path

Checklist

  • Conventional Commits title
  • Branch name: codex/<primary>-<type>-<slug>
  • All review gates passed: spec → quality → architecture → test (+ security if triggered) — review pending.
  • /gh-review --local-diff main...HEAD run locally — pending.

Reviewer notes

This is the skeleton initial PR for issue #38. It satisfies two of the three acceptance criteria:

  • ✅ Real URLs route through ChromiumDriver and produce real-rule violations.
  • ✅ Typed errors for Chromium-not-found / unsupported-version / malformed-snapshot / driver fault map onto CallToolResult::error with deterministic text.
  • ⏭ Protocol test against http://localhost:<N>/fixture.html is deferred to a follow-up milestone — that test needs a Chromium binary on the runner and exercises the real CDP path end-to-end. Out of scope for this skeleton.

The existing mcp_lint_url_returns_structured_content happy-path test (uses plumb-fake://hello) keeps CI green without Chromium. The --executable-path plumbing through MCP args and pre-warmed browser reuse on PlumbServer are tracked separately in batch 3B.

Branch on `is_fake_url` inside the `lint_url` tool: `plumb-fake://`
URLs continue to serve `PlumbSnapshot::canned()` for tests, while
`http(s)://` URLs are routed through `ChromiumDriver` against a fixed
desktop target (1280x800, DPR 1.0). Driver failures surface as
`CallToolResult::error` with the typed `CdpError` message — no
unwrap, no panic.

Refs #38. The `http://localhost:<N>/fixture.html` protocol test that
boots Chromium is deferred to a follow-up milestone since it requires
a Chromium binary on the runner; the existing
`mcp_lint_url_returns_structured_content` happy path (using
`plumb-fake://hello`) keeps CI green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@aram-devdocs
aram-devdocs merged commit 2924d5d into main Apr 26, 2026
3 of 4 checks passed
@aram-devdocs
aram-devdocs deleted the codex/38-feat-mcp-lint-url-real-urls branch April 26, 2026 18:24
@aram-devdocs

Copy link
Copy Markdown
Owner Author

No description provided.

@github-actions

Copy link
Copy Markdown
Contributor

I have everything I need for a complete review. Here's my assessment:


Review: feat(mcp): lint_url accepts real http(s) URLs via ChromiumDriver

Files reviewed: crates/plumb-mcp/src/lib.rs, crates/plumb-mcp/Cargo.toml, crates/plumb-cli/tests/mcp_stdio.rs, docs/src/mcp.md, crates/plumb-mcp/AGENTS.md


1. Determinism

No determinism violations. The new code path runs through ChromiumDriver::snapshot, which the existing plumb-cdp rules govern. No wall-clock values or HashMap iteration leak into tool responses.


2. Workspace layering

crates/plumb-mcp/Cargo.toml:19 — rule doc not updated. The normative rule at .agents/rules/dependency-hierarchy.md:16 states:

plumb-mcp — MCP server. Depends on: plumb-core, plumb-format.

Adding plumb-cdp to plumb-mcp's deps doesn't create a cycle (the layer hierarchy flows correctly: layer 5 depending on layer 3 is fine). But the normative rule doc was not updated. crates/plumb-mcp/AGENTS.md was updated correctly; .agents/rules/dependency-hierarchy.md was not. One canonical source per invariant — the rule doc is now stale.


3. Error handling

No unwrap/expect in the new library code. #![deny(clippy::unwrap_used, clippy::expect_used)] is present at line 23. The Err arm at lib.rs:114–118 surfaces the typed CdpError message via CallToolResult::error. Clean.


4. Anti-pattern violation — new Chromium process per call (BLOCKER)

crates/plumb-mcp/src/lib.rs:111:

let driver = ChromiumDriver::new(ChromiumOptions::default());
match driver.snapshot(target).await {

plumb-cdp/CLAUDE.md explicitly lists this shape in Anti-patterns:

Spawning a new browser per snapshot call from the MCP server — plumb-mcp owns browser warmth; expose a constructor that accepts a pre-warmed Browser handle.

ChromiumDriver::snapshot_all at plumb-cdp/src/lib.rs:290 calls ChromiumSession::launch(config) and shuts down at line 303. Every lint_url call currently launches and terminates a full Chromium process — cold starts of 3–8 seconds per request in practice, serialized and unrecoverable if the binary isn't present.

The pre-warmed constructor path described in the anti-pattern note doesn't yet exist in plumb-cdp (no from_browser or similar). The fix is either: (a) hold the driver in PlumbServer so it's created once per server lifetime, not once per call; or (b) add a pre-warmed constructor to ChromiumDriver and share the browser handle. Without one of these, the lint_url tool will be unusable at interactive latency, and a missing Chromium binary silently drops every request with no warmup-path error distinguishable from a page error.


5. Test coverage

crates/plumb-cli/tests/mcp_stdio.rs — the http(s):// code path (lines 103–119 in lib.rs) has zero test coverage. The commit message explicitly defers the http://localhost:<N>/fixture.html integration test. However, testing.md says to gate slow or external-binary tests via #[cfg(feature = "slow-tests")], not to defer them without a tracking issue or feature gate. A bare TODO in a commit message is not a tracking artifact. At minimum, a #[cfg(feature = "chromium-tests")]-gated skeleton or a tracking issue number should be present.


6. Docs accuracy

docs/src/mcp.md:40 claims isError: true on Chromium failure. The code returns CallToolResult::error(...) which maps to isError: true in the MCP wire format — this is correct.

No AI-tell phrases detected in the doc changes. The humanizer gate is clean.


Punch list

# Severity Location Issue
1 BLOCKER plumb-mcp/src/lib.rs:111 New ChromiumDriver per call violates the documented anti-pattern; each call cold-starts Chromium
2 REQUEST_CHANGES .agents/rules/dependency-hierarchy.md:16 Rule doc says plumb-mcp depends on plumb-core, plumb-formatplumb-cdp not listed; update the normative rule
3 REQUEST_CHANGES crates/plumb-cli/tests/mcp_stdio.rs http(s):// code path has no test; deferred work needs a feature-gated skeleton or tracking issue reference, not an unlinked commit-message note

Verdict: BLOCK

This was referenced Apr 27, 2026
aram-devdocs added a commit that referenced this pull request Apr 27, 2026
Resolves the conflicts that arose after PR #131 (lint_url over real
http(s)://) and PR #133 (delegation-guard merge bypass) merged into
main while this branch carried PR #132 (get_config tool).

- crates/plumb-mcp/src/lib.rs: keep both new use lines —
  plumb_cdp::{BrowserDriver, ChromiumDriver, ChromiumOptions, Target,
  is_fake_url} from main and plumb_config::ConfigError from this
  branch. Bring in ViewportKey alongside Config/PlumbSnapshot/etc.
- Cargo.lock: union plumb-cdp and plumb-config under plumb-mcp.

Note: local cargo check is blocked by a disk-space failure in the
sandbox toolchain ('No space left on device' while syncing the
1.95.0 channel). Verification deferred to CI.
aram-devdocs added a commit that referenced this pull request Apr 27, 2026
Merge main containing PR #130 (explain_rule) and PR #131 (lint_url
real http URLs) into the list_rules branch. Also resolve doc-only
conflict in docs/src/mcp.md.

Conflicts in plumb-mcp/src/lib.rs and tests/mcp_protocol.rs are
mechanical: both branches added a new tool (`explain_rule` on main,
`list_rules` on this branch). The resolution unions both: 4 tools
(echo, lint_url, explain_rule, list_rules) registered in
list_tools, dispatched in call_tool, and exercised by the test
suite. register_builtin import switched to the top-level
re-export (plumb_core::register_builtin) introduced on main; the
list_rules test now uses the same import rather than the
deprecated plumb_core::rules::register_builtin path.

Local cargo check skipped — host disk full while syncing toolchain
1.95.0; CI will verify.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

feat(mcp): lint_url accepts real URLs (not just plumb-fake://)

1 participant