MCP server: modelcraft-cli mcp (headless or bridged), read/write roots, end-to-end analysis over MCP (#48)

* io: read and write roots that confine an automation client's file access

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: every file a command reads or writes is checked against the session's roots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: headless pictures of a session (model, geometry, deformed shape and contours)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mcp: MCP server over stdio with headless and connected backends, roots and caps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* cli: modelcraft-cli mcp [--connect ADDR | --headless] with automation read and write roots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mcp: end-to-end analysis of a corpus part over MCP (open, tet mesh, define, solve, probe, render)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: MCP server (docs/mcp.md), Claude Code setup, roots; architecture, README, roadmap, attribution

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 22:18:08 -04:00
1 parent e900f41ce3
commit 0899587127
29 files changed
+2405 -49

No files matched your search

+15 -1
View File
@@ -18,6 +18,7 @@ pub mod cmd;
pub mod history;
pub mod results;
pub mod sample;
pub mod view;
use std::collections::BTreeMap;
use std::sync::Arc;
@@ -174,13 +175,17 @@ pub struct Session {
pub results_view: Option<results::ResultsView>,
/// The default work caps of commands; a file replacing the session keeps them.
pub limits: Limits,
/// The folders commands may read and write files in (an automation client's roots,
/// docs/mcp.md); unconfined by default. A file replacing the session keeps them.
pub file_access: modelcraft_io::roots::Roots,
}
impl Session {
/// Run a command. On an error or a panic the model, the design and the surfaces are left as
/// they were.
pub fn execute(&mut self, id: &str, params: &Value) -> Result<Value> {
let spec = find_command(id).ok_or_else(|| EngineError::UnknownCommand(id.to_string()))?;
// An id from hostile input is not echoed in full.
let spec = find_command(id).ok_or_else(|| EngineError::UnknownCommand(id.chars().take(100).collect()))?;
// Session commands (open, save, undo, redo) change the session only in their last step,
// after everything that can fail, so they need no copy of the model to restore.
let before = (spec.kind == CommandKind::Model).then(|| self.snapshot());
@@ -255,6 +260,15 @@ impl Session {
self.results.insert(result.step().to_string(), (self.revision, Arc::new(result)));
}
/// The path to use for `access` to the file at `path`, checked against [`Session::file_access`].
pub fn checked_path(&self, path: &str, access: modelcraft_io::roots::Access) -> Result<String> {
let checked = self.file_access.check(std::path::Path::new(path), access)?;
match checked.to_str() {
Some(p) => Ok(p.to_string()),
None => Err(EngineError::Params(format!("`{path}` resolves to a path that is not valid UTF-8"))),
}
}
/// The current state is what `path` now holds.
pub(crate) fn mark_saved(&mut self, path: String) {
self.path = Some(path);