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
2 changes: 2 additions & 0 deletions docs/content/docs/reference/cua-driver/mcp-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ Caveats:

Update cua-driver-rs configuration. Changes to `capture_mode` and `max_image_dimension` take effect immediately. The `experimental_pip` keys are persisted to `~/.cua-driver/config.json` and take effect on the next daemon restart.

Two equivalent argument shapes are accepted on every platform: a **direct field** (`{"capture_scope": "desktop"}`) or a **`{key, value}` pair** (`{"key": "capture_scope", "value": "desktop"}`, the same shape the CLI `config set` uses). A direct field wins if both are supplied.

Comment on lines +527 to +528

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the argument table to include capture_scope, key, and value.

The new paragraph documents the alternate shape, but the reference table below still omits those inputs, so the docs no longer fully match the tool contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/content/docs/reference/cua-driver/mcp-tools.mdx` around lines 527 - 528,
Update the argument table in the MCP tools docs to list capture_scope, key, and
value so it matches the accepted inputs described in the surrounding paragraph.
Make sure the table reflects both supported shapes for the config tool contract,
and keep the existing direct-field vs {key, value} behavior aligned with the
documentation.

**Per-session isolation (daemon-proxy path).** On the daemon-proxy path, `set_config` writes an in-memory, session-scoped override that does not touch the global `DriverConfig` or persist to disk. `get_config` and capture tools resolve effective values as: call-arg > session override > global default. The override is dropped automatically when the client disconnects. Only the anonymous path (`cua-driver config set` CLI, one-shot `cua-driver call`) writes the persisted global default.

| Argument | Type | Required | Description |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ fn def() -> &'static ToolDef {
input_schema: serde_json::json!({
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Name of a single config field to write ({key, value} shape, \
matching the CLI `config set` and the Windows/Linux tools). Pair with `value`. \
Equivalent to passing the field directly."
},
"value": {
"description": "New value for `key`. JSON type depends on the key."
},
"capture_mode": {
"type": "string",
"enum": ["som", "vision", "ax"],
Expand Down Expand Up @@ -75,20 +84,36 @@ impl Tool for SetConfigTool {
// sessions don't clobber each other or the persisted default.
let session_id = args.opt_str("_session_id");

// Accept BOTH shapes, matching Windows/Linux + the CLI `config set`:
// - direct fields: {"capture_scope":"desktop"}
// - {key, value}: {"key":"capture_scope","value":"desktop"}
// A direct field wins if both are somehow present.
let kv: Option<(String, Value)> = args
.opt_str("key")
.and_then(|k| args.get("value").map(|v| (k, v.clone())));
let kv_str = |name: &str| -> Option<String> {
kv.as_ref()
.filter(|(k, _)| k == name)
.and_then(|(_, v)| v.as_str().map(str::to_owned))
};
let kv_u64 = |name: &str| -> Option<u64> {
kv.as_ref().filter(|(k, _)| k == name).and_then(|(_, v)| v.as_u64())
};

// Validate max_image_dimension up front so both branches share the
// u32 check and we never half-apply.
let max_dim: Option<u32> = match args.opt_u64("max_image_dimension") {
let max_dim: Option<u32> = match args.opt_u64("max_image_dimension").or_else(|| kv_u64("max_image_dimension")) {
Some(dim) => match u32::try_from(dim) {
Ok(d) => Some(d),
Err(_) => return ToolResult::error(format!("max_image_dimension {dim} exceeds u32::MAX")),
},
None => None,
};
let capture_mode = args.opt_str("capture_mode");
let capture_mode = args.opt_str("capture_mode").or_else(|| kv_str("capture_mode"));

// Validate capture_scope up front so both branches share the check and
// we never half-apply an invalid value.
let capture_scope = args.opt_str("capture_scope");
let capture_scope = args.opt_str("capture_scope").or_else(|| kv_str("capture_scope"));
Comment on lines +87 to +116

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle {key, value} for the PiP settings too, or reject them explicitly.

The new fallback only feeds capture_mode, capture_scope, and max_image_dimension. experimental_pip and experimental_pip_geometry are still read from direct fields only later in invoke, so requests like {"key":"experimental_pip","value":true} now validate against the schema/docs but still no-op on macOS. That breaks the “equivalent argument shapes” contract this PR is adding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/platform-macos/src/tools/set_config.rs` around
lines 87 - 116, The new `{key, value}` fallback in `invoke` only covers
`capture_mode`, `capture_scope`, and `max_image_dimension`, so PiP-related
settings can still silently no-op on macOS. Update `set_config.rs` so
`experimental_pip` and `experimental_pip_geometry` are also read from the same
`kv` fallback path, or explicitly reject `{key, value}` for those options with a
clear error. Keep the handling consistent with the existing `kv_str`/`kv_u64`
helpers and the direct-field precedence used for the other config keys.

Comment on lines +112 to +116

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate capture_mode on the {key, value} path.

kv_str("capture_mode") accepts any string, but the direct-field shape is constrained to ["som", "vision", "ax"]. That means {"key":"capture_mode","value":"anything"} can persist an invalid mode that the direct-field contract would reject. Please mirror the capture_scope check here so both input shapes enforce the same values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/platform-macos/src/tools/set_config.rs` around
lines 112 - 116, Validate capture_mode in set_config’s capture_mode handling so
the {key, value} path enforces the same allowed values as the direct field.
Mirror the existing capture_scope validation near the capture_mode lookup in the
relevant config parsing flow, ensuring both args.opt_str("capture_mode") and
kv_str("capture_mode") reject any value outside ["som", "vision", "ax"] before
persisting it.

if let Some(scope) = capture_scope.as_deref() {
if scope != "window" && scope != "desktop" {
return ToolResult::error(format!(
Expand Down
Loading