Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/plumb-cli/tests/mcp_stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ fn mcp_initialize_and_tools_list() {
.unwrap_or_else(|| panic!("lint_url tool missing: got {tools:?}"));
assert_eq!(
lint_url["description"],
"Lint a URL with Plumb. Walking-skeleton accepts plumb-fake:// URLs only."
"Lint a URL with Plumb. Accepts http(s):// and plumb-fake:// URLs."
);
assert_eq!(
lint_url["inputSchema"]["properties"]["url"]["type"],
Expand Down
3 changes: 2 additions & 1 deletion crates/plumb-mcp/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ Use the `09-mcp-tool-author` subagent for cookie-cutter execution.
## Depends on

- `plumb-core` (types; `test-fake` feature enabled so `lint_url` can
serve the canned snapshot until the real CDP driver lands).
serve the canned snapshot for `plumb-fake://` URLs).
- `plumb-cdp` (drives Chromium for real `http(s)://` URLs in `lint_url`).
- `plumb-format` (mcp_compact).
- `rmcp` (server + macros + transport-io + schemars features).
- `tokio`, `serde`, `serde_json`, `schemars`, `thiserror`, `tracing`.
Expand Down
5 changes: 3 additions & 2 deletions crates/plumb-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ keywords.workspace = true
categories.workspace = true

[dependencies]
# `test-fake` is enabled so the walking-skeleton `lint_url` tool can
# serve the canned snapshot until the real Chromium driver lands.
# `test-fake` keeps the canned snapshot path available for
# `plumb-fake://` URLs alongside the real Chromium driver path.
plumb-cdp = { workspace = true }
plumb-core = { workspace = true, features = ["test-fake"] }
plumb-format = { workspace = true }
rmcp = { workspace = true }
Expand Down
38 changes: 27 additions & 11 deletions crates/plumb-mcp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
//!
//! - `echo` — smoke-tests the transport.
//! - `lint_url` — lints a URL and returns violations in the MCP-compact
//! shape from `docs/local/prd.md` §14.2. Walking-skeleton accepts
//! `plumb-fake://` URLs only.
//! shape from `docs/local/prd.md` §14.2. Accepts `http(s)://` URLs
//! (driven by `plumb_cdp::ChromiumDriver`) and `plumb-fake://` URLs
//! (served from the canned snapshot).
//! - `explain_rule` — returns the canonical markdown documentation and
//! metadata for a built-in rule by id.
//!
Expand All @@ -28,7 +29,8 @@ pub use explain::rule_ids as documented_rule_ids;

use std::io;

use plumb_core::{Config, PlumbSnapshot, register_builtin, run};
use plumb_cdp::{BrowserDriver, ChromiumDriver, ChromiumOptions, Target, is_fake_url};
use plumb_core::{Config, PlumbSnapshot, ViewportKey, register_builtin, run};
use plumb_format::mcp_compact;
use rmcp::{
RoleServer, ServerHandler, ServiceExt,
Expand Down Expand Up @@ -67,7 +69,7 @@ pub struct EchoArgs {
/// Arguments to the `lint_url` tool.
#[derive(Debug, Deserialize, JsonSchema)]
pub struct LintUrlArgs {
/// URL to lint. Walking-skeleton accepts `plumb-fake://` URLs only.
/// URL to lint. Accepts `http(s)://` and `plumb-fake://` URLs.
pub url: String,
}

Expand Down Expand Up @@ -96,12 +98,26 @@ impl PlumbServer {
}

async fn lint_url(&self, args: LintUrlArgs) -> Result<CallToolResult, ErrorData> {
if !args.url.starts_with("plumb-fake://") {
return Ok(CallToolResult::success(vec![Content::text(
"lint_url currently only accepts plumb-fake:// URLs in the walking skeleton.",
)]));
}
let snapshot = PlumbSnapshot::canned();
let snapshot = if is_fake_url(&args.url) {
PlumbSnapshot::canned()
} else {
let target = Target {
url: args.url.clone(),
viewport: ViewportKey::new("desktop"),
width: 1280,
height: 800,
device_pixel_ratio: 1.0,
};
let driver = ChromiumDriver::new(ChromiumOptions::default());
match driver.snapshot(target).await {
Ok(snap) => snap,
Err(err) => {
return Ok(CallToolResult::error(vec![Content::text(format!(
"lint_url failed: {err}"
))]));
}
}
};
let config = Config::default();
let violations = run(&snapshot, &config);
let (text, structured) = mcp_compact(&violations);
Expand Down Expand Up @@ -202,7 +218,7 @@ impl ServerHandler for PlumbServer {
tool_descriptor::<EchoArgs>("echo", "Echo a message — smoke test the MCP transport."),
tool_descriptor::<LintUrlArgs>(
"lint_url",
"Lint a URL with Plumb. Walking-skeleton accepts plumb-fake:// URLs only.",
"Lint a URL with Plumb. Accepts http(s):// and plumb-fake:// URLs.",
),
tool_descriptor::<ExplainRuleArgs>(
"explain_rule",
Expand Down
4 changes: 2 additions & 2 deletions docs/src/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ For local development against a source checkout:
}
```

## Walking-skeleton tools
## Tools

| Tool | Description |
|------|-------------|
| `echo` | Smoke-test the transport. Echoes the `message` arg back. |
| `lint_url` | Lint a URL. Accepts `plumb-fake://hello` only until the Chromium driver lands. |
| `lint_url` | Lint a URL. Accepts `http(s)://` URLs (driven by the bundled Chromium driver) and `plumb-fake://hello` (canned snapshot for tests). On a Chromium launch failure the response is returned with `isError: true` and a single text block carrying the typed driver error. |
| `explain_rule` | Return canonical documentation and metadata for a Plumb rule by id. Args: `{ "rule_id": "<category>/<id>" }`. |

The response shape follows the MCP `content` + `structuredContent`
Expand Down
Loading