Skip to content

fix: improve Hyprland 0.55 and MCP schema compatibility#45

Merged
avifenesh merged 1 commit into
agent-sh:mainfrom
LikelyLucid:fix/hyprland-055-schema-formats
Jul 14, 2026
Merged

fix: improve Hyprland 0.55 and MCP schema compatibility#45
avifenesh merged 1 commit into
agent-sh:mainfrom
LikelyLucid:fix/hyprland-055-schema-formats

Conversation

@LikelyLucid

Copy link
Copy Markdown
Contributor

Summary

  • use Hyprland 0.55's Lua focus dispatcher before falling back to the legacy focuswindow dispatcher
  • remove Rust-specific unsigned integer format annotations from exported MCP input and output schemas
  • add regression coverage for both the Lua expression and exported tool schemas

Motivation

Hyprland 0.55.4 rejects the legacy command used for exact window targeting:

hyprctl dispatch focuswindow address:0x...
')' expected near 'address'

The supported 0.55 form is:

hyprctl dispatch 'hl.dsp.focus({ window = "address:0x..." })'

The Lua-first/legacy-fallback order keeps older Hyprland releases working.

Separately, schemars emits uint, uint8, uint32, and uint64 formats throughout tool schemas. These are not standard JSON Schema formats and produce repeated warnings in MCP clients. The export-boundary sanitizer removes only those format annotations while preserving integer types and numeric constraints.

Validation

Manually validated on Hyprland 0.55.4, including cross-workspace exact focus.

cargo fmt --all -- --check
cargo check --locked --all-targets
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked --no-fail-fast
python3 scripts/mcp_safety_check.py

Results: 134 library tests passed; MCP safety check passed for all 18 tools. The schema regression test confirms no unsupported unsigned formats remain in either input or output schemas.

@LikelyLucid LikelyLucid requested a review from avifenesh as a code owner July 13, 2026 06:41

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces schema sanitization to remove unsupported unsigned integer formats from exported tool schemas in src/server.rs, along with corresponding unit tests. It also updates window activation in src/windowing/backends/hyprland.rs to try a new Lua-based focus dispatcher before falling back to the legacy command. Feedback suggests simplifying the recursion logic in sanitize_unsigned_integer_formats by matching directly on the serde_json::Value enum to make the traversal cleaner and more robust.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/server.rs
Comment on lines +69 to +93
fn sanitize_unsigned_integer_formats(value: &mut serde_json::Value) {
let serde_json::Value::Object(object) = value else {
return;
};

let has_unsigned_format = matches!(
object.get("format").and_then(serde_json::Value::as_str),
Some("uint" | "uint8" | "uint16" | "uint32" | "uint64" | "usize")
);
if has_unsigned_format {
object.remove("format");
}

for nested in object.values_mut() {
match nested {
serde_json::Value::Object(_) => sanitize_unsigned_integer_formats(nested),
serde_json::Value::Array(items) => {
for item in items {
sanitize_unsigned_integer_formats(item);
}
}
_ => {}
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The recursion logic in sanitize_unsigned_integer_formats can be simplified and made more robust by matching directly on the serde_json::Value enum, similar to how it is done in the test helper collect_unsigned_integer_formats. This avoids the nested match inside the loop and ensures that any nested array-of-arrays or other structures are correctly traversed.

fn sanitize_unsigned_integer_formats(value: &mut serde_json::Value) {
    match value {
        serde_json::Value::Object(object) => {
            let has_unsigned_format = matches!(
                object.get("format").and_then(serde_json::Value::as_str),
                Some("uint" | "uint8" | "uint16" | "uint32" | "uint64" | "usize")
            );
            if has_unsigned_format {
                object.remove("format");
            }
            for nested in object.values_mut() {
                sanitize_unsigned_integer_formats(nested);
            }
        }
        serde_json::Value::Array(items) => {
            for item in items {
                sanitize_unsigned_integer_formats(item);
            }
        }
        _ => {}
    }
}

@avifenesh

Copy link
Copy Markdown
Collaborator

Thanks @LikelyLucid — this is a great contribution. The Lua-first/legacy-fallback focus order is exactly the right compatibility shape, and stripping the non-standard schemars uint formats at the export boundary (with regression coverage) cleans up a real annoyance for MCP clients. CI is fully green, merging. This will go out in the next release shortly.

@avifenesh avifenesh merged commit 72a325c into agent-sh:main Jul 14, 2026
15 checks passed
avifenesh added a commit that referenced this pull request Jul 14, 2026
… schema fixes

Backfills CHANGELOG entries for 0.3.0 and 0.3.1 and adds the 0.4.0 section
covering the generic X11/EWMH window backend (#41), the pi coding agent
extension package (#42), the Hyprland 0.55 Lua focus dispatcher with legacy
fallback plus schemars unsigned-format sanitization at the MCP export
boundary (#45), and the Ubuntu GNOME extension bootstrap fix (#40).
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.

2 participants