Files
vectorcraft/crates/mcp/src/lib.rs
T
vangel76andClaude Opus 5.5 9122ed0a7e MCP: cancel long exports, answer while one runs, report progress (#2165, #2171, #2172)
The MCP server read one line, ran it and answered it before reading the next,
so notifications/cancelled was never seen during a call and every request
(even ping) waited behind a running export.

- vectorcraft_doc::cancel: a thread-local cancel token and progress sink that
  long work polls. Checkpoints: PDF pages, PDF flattening, geometry bake,
  raster-effect flattening, drawing objects, raster artboards, and before any
  file is written. A cancelled command returns EngineError::Cancelled and
  changes nothing.
- Server::serve reads on its own thread: notifications/cancelled drops a
  queued request or stops the running one (no reply, as the MCP spec says),
  engine-free requests (ping, the list methods, prompts/get) are answered at
  once, a request that would wait behind a call running for 2 s is refused
  with -32003 naming that call, and _meta.progressToken gets
  notifications/progress (page n of m). Queue and output are bounded.
- The remote backend stops waiting for the app when the call is cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:49:48 +07:00

72 lines
1.9 KiB
Rust

//! VectorCraft'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 VectorCraft as a set of MCP
//! tools and resources and forwards everything to a [`Backend`]:
//!
//! - [`Remote`] talks to a running desktop app through its loopback JSON-lines control channel
//! (`vectorcraft --control 7979`): one `{"id","method","params"}` line in, one
//! `{"id","ok","result"|"error"}` line out.
//! - [`Headless`] hosts an in-process [`vectorcraft_engine::Session`] and implements the same
//! control-channel method names itself (rendering screenshots with `vectorcraft-render`), so agents
//! can draw and look at the result without a window.
//!
//! Entry points: [`Server::serve`] (stdio loop) and [`Server::handle_line`] (one message).
#![forbid(unsafe_code)]
mod backend;
mod headless;
mod intake;
pub mod logging;
mod prompts;
mod resources;
mod server;
mod tools;
pub use backend::{Backend, Remote};
pub use headless::Headless;
pub use prompts::{PROMPTS, PromptArg, PromptDef};
pub use resources::{DOC_JSON_URI, DOC_URI, TEMPLATES};
pub use server::{PROTOCOL_VERSION, Server};
pub use tools::{ToolResult, call_tool, tool_definitions};
/// Default control-channel address of the desktop app.
pub const DEFAULT_ADDR: &str = "127.0.0.1:7979";
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_batchdraw;
#[cfg(test)]
mod tests_concurrency;
#[cfg(test)]
mod tests_conventions;
#[cfg(test)]
mod tests_distortkeys;
#[cfg(test)]
mod tests_drawopts;
#[cfg(test)]
mod tests_exportas;
#[cfg(test)]
mod tests_fileio;
#[cfg(test)]
mod tests_freeform;
#[cfg(test)]
mod tests_gradient;
#[cfg(test)]
mod tests_limits;
#[cfg(test)]
mod tests_links;
#[cfg(test)]
mod tests_liquify;
#[cfg(test)]
mod tests_pen;
#[cfg(test)]
mod tests_persp;
#[cfg(test)]
mod tests_place;
#[cfg(test)]
mod tests_protocol;
#[cfg(test)]
mod tests_svg;