From 5171717e7188870c1aad7cc36b9b4f4a5c183b1f Mon Sep 17 00:00:00 2001 From: Seamile Date: Sat, 10 Oct 2026 12:05:56 +0800 Subject: [PATCH] fix(mcp): accept explicit font directories before startup --- README.md | 1 + crates/obscura-cli/src/main.rs | 21 ++++++++++++++++ crates/obscura-cli/tests/mcp_client.rs | 33 ++++++++++++++++++++++++++ docs/CLI-reference.md | 1 + docs/Use-the-MCP-server.md | 12 ++++++++++ 5 files changed, 68 insertions(+) diff --git a/README.md b/README.md index 6ccf4b2..8e16b91 100644 --- a/README.md +++ b/README.md @@ -535,6 +535,7 @@ Optional flags (both transports): |------|-------------| | `--proxy ` | HTTP/SOCKS5 proxy | | `--user-agent ` | Custom User-Agent string | +| `--font-dir ` | Load additional fonts before rendering (repeatable; render build) | | `--stealth` | Enable anti-detection mode | ### Claude Desktop config diff --git a/crates/obscura-cli/src/main.rs b/crates/obscura-cli/src/main.rs index e0522bc..e895aff 100644 --- a/crates/obscura-cli/src/main.rs +++ b/crates/obscura-cli/src/main.rs @@ -193,6 +193,10 @@ enum Command { }, Mcp { + /// Recursively load additional font files before the first render. + #[arg(long = "font-dir", value_name = "DIR")] + font_dirs: Vec, + #[arg(long)] http: bool, @@ -587,12 +591,14 @@ async fn run_cli() -> anyhow::Result<()> { .await?; } Some(Command::Mcp { + font_dirs, http, host, port, proxy, user_agent, }) => { + configure_font_directories(&font_dirs)?; let mcp_proxy = merge_proxy(global_proxy.clone(), proxy); if http { obscura_mcp::http::run(host, port, mcp_proxy, user_agent, stealth).await?; @@ -2497,6 +2503,21 @@ mod tests { } } + #[test] + fn parsed_mcp_accepts_repeated_font_directories() { + let args = Args::try_parse_from([ + "obscura", "mcp", "--font-dir", "/fonts/cjk", "--font-dir", "/fonts/brand", + ]) + .expect("clap should accept MCP font directories"); + match args.command { + Some(Command::Mcp { font_dirs, .. }) => assert_eq!( + font_dirs, + [std::path::PathBuf::from("/fonts/cjk"), std::path::PathBuf::from("/fonts/brand")] + ), + _ => panic!("expected Mcp command"), + } + } + #[test] fn no_subcommand_is_not_quiet() { assert!(!is_quiet_command(&None)); diff --git a/crates/obscura-cli/tests/mcp_client.rs b/crates/obscura-cli/tests/mcp_client.rs index c0f61e0..08767be 100644 --- a/crates/obscura-cli/tests/mcp_client.rs +++ b/crates/obscura-cli/tests/mcp_client.rs @@ -7,6 +7,39 @@ use std::thread::JoinHandle; use std::time::Duration; const OBSCURA: &str = env!("CARGO_BIN_EXE_obscura"); + +#[test] +fn mcp_font_directory_is_validated_before_either_transport_starts() { + let missing = std::env::temp_dir().join(format!("obscura-missing-fonts-{}", std::process::id())); + assert!(!missing.exists()); + for http in [false, true] { + let mut command = Command::new(OBSCURA); + command.args(["mcp", "--font-dir"]).arg(&missing).stdin(Stdio::null()); + if http { + command.args(["--http", "--port", "0"]); + } + let output = command.output().expect("start MCP with invalid font directory"); + assert!(!output.status.success()); + assert!(String::from_utf8_lossy(&output.stderr).contains("Font directory does not exist")); + } +} + +#[test] +fn mcp_font_directory_respects_render_feature() { + let output = Command::new(OBSCURA) + .args(["mcp", "--font-dir"]) + .arg(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests")) + .stdin(Stdio::null()) + .output() + .expect("start MCP with existing font directory"); + if cfg!(feature = "render") { + assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr)); + } else { + assert!(!output.status.success()); + assert!(String::from_utf8_lossy(&output.stderr).contains("--font-dir requires a render-enabled build")); + } +} + const TEST_PAGE: &str = r#"Example Domain

Example Domain

Deterministic local MCP fixture.

More information..."#; diff --git a/docs/CLI-reference.md b/docs/CLI-reference.md index 4928575..d621c9e 100644 --- a/docs/CLI-reference.md +++ b/docs/CLI-reference.md @@ -112,6 +112,7 @@ Run obscura as an MCP server. ``` --http HTTP transport instead of stdio + --font-dir Load additional fonts before rendering (repeatable; render build) --host HTTP bind host (default 127.0.0.1) --port HTTP port (default 3000) --proxy HTTP or SOCKS5 proxy diff --git a/docs/Use-the-MCP-server.md b/docs/Use-the-MCP-server.md index 9bb46bc..75caff8 100644 --- a/docs/Use-the-MCP-server.md +++ b/docs/Use-the-MCP-server.md @@ -1,5 +1,17 @@ `obscura mcp` exposes obscura as a Model Context Protocol server so MCP-capable clients (Claude Desktop, Claude Code, etc.) can drive it. +Render-enabled builds can load additional fonts for screenshots and PDF output: + +```bash +obscura mcp --font-dir /path/to/fonts +``` + +Repeat `--font-dir` to load multiple directories. It recursively loads TTF, TTC, +OTF, and OTC files once before rendering, using the same opt-in font database as +`obscura serve`. System fonts are not scanned automatically. Invalid directories +fail at startup, and no-render builds reject a nonempty font directory option. +The option works with both stdio and HTTP transports. + ## Run Stdio (default, for direct client integration):