Files
figcraft/crates/mcp/src/lib.rs
BFlat 10a9b7b165 M8: Windows QA fixes (#131)
Found and fixed in a Windows pass of everyday workflows: unsaved documents are now copied in the background (at most every 10 s) and offered back after a crash or forced quit (file.recover); Ctrl+arrow / Ctrl+Backspace / Ctrl+Delete text motion follows Windows and Linux conventions (keymap::text_motion); number-field scrubbing keeps its value; typing in a Tab-focused field works; default linear gradients and their handles behave; Escape closes dialogs without deselecting; clipped panel hints wrap; the unsaved-changes dialog is translated.
2026-10-11 18:20:38 +07:00

42 lines
1.7 KiB
Rust

//! FigCraft's MCP server.
//!
//! [Model Context Protocol](https://modelcontextprotocol.io) over stdio: newline-delimited JSON-RPC
//! 2.0, hand-written (no async runtime). The server exposes FigCraft as MCP tools, resources and
//! prompts, and forwards everything to a [`Backend`]:
//!
//! - [`Headless`] hosts an in-process [`figcraft_engine::Session`] and answers the control-channel
//! methods itself (rendering with `figcraft-render`), so agents can design without a window.
//! - [`Attached`] talks to a running desktop app through its loopback JSON-lines control channel
//! (`figcraft --control PORT`): one `{"id","method","params"}` line in, one
//! `{"id","ok","result"|"error"}` line out.
//!
//! Entry points: [`Server::serve`] (stdio loop) and [`Server::handle_line`] (one message).
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
mod backend;
mod headless;
mod prompts;
mod resources;
mod server;
mod tools;
#[cfg(test)]
mod gradient_tests;
pub use backend::{Attached, Backend, decode_data, page_children};
pub use headless::Headless;
pub use prompts::{PROMPTS, PromptArg, PromptDef};
pub use resources::{COMMAND_TEMPLATE, COMMANDS_URI, DOC_URI, NODE_TEMPLATE};
pub use server::{INSTRUCTIONS, PROTOCOL_VERSION, SUPPORTED_VERSIONS, Server};
pub use tools::{TOOL_NAMES, ToolResult, call_tool, tool_definitions};
/// Width and height of a PNG from its IHDR chunk.
pub fn png_size(bytes: &[u8]) -> Option<(u32, u32)> {
if bytes.get(..8)? != b"\x89PNG\r\n\x1a\n" || bytes.get(12..16)? != b"IHDR" {
return None;
}
let w = u32::from_be_bytes(bytes.get(16..20)?.try_into().ok()?);
let h = u32::from_be_bytes(bytes.get(20..24)?.try_into().ok()?);
Some((w, h))
}