MCP: set_property and add_keyframe take timeBase too (#522 follow-up)

Issue #257 names these tools: an agent can now give comp times on an
offset layer through MCP, as it can through prop.addKey and keys.select.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
This commit is contained in:
Claude committed 2026-10-10 12:59:03 +00:00
1 parent 195b862036
commit 42ad4edaee
2 files changed
+27 -1

No files matched your search

+14
View File
@@ -153,6 +153,20 @@ fn headless_workflow() {
let mid = call_json(&mut s, "get_property", json!({"layer": l, "path": "transform/position", "time": 1.0}));
assert!((mid["value"][0].as_f64().unwrap() - 160.0).abs() < 1.0, "{mid}");
// Comp-time keys on an offset layer (#257): the key lands where the Timeline shows it.
call_json(&mut s, "execute_command", json!({"command": "layer.timing", "params": {"layer": l, "start": 1.0}}));
let p = call_json(
&mut s,
"add_keyframe",
json!({"layer": l, "path": "transform/scale", "keys": [{"time": 1.5, "value": [50, 50]}, {"time": 2.5, "value": [100, 100]}], "timeBase": "comp", "interpolation": "hold"}),
);
let times: Vec<f64> = p["keys"].as_array().unwrap().iter().map(|k| k["time"].as_f64().unwrap()).collect();
assert!((times[0] - 0.5).abs() < 1e-6 && (times[1] - 1.5).abs() < 1e-6, "layer-time keys: {times:?}");
assert_eq!(p["keys"][0]["out"], "Hold", "the comp-time keys were selected for the interpolation");
call_json(&mut s, "set_property", json!({"layer": l, "path": "transform/scale", "value": [75, 75], "time": 3.0, "timeBase": "comp"}));
let p = call_json(&mut s, "get_property", json!({"layer": l, "path": "transform/scale"}));
assert!(p["keys"].as_array().unwrap().iter().any(|k| (k["time"].as_f64().unwrap() - 2.0).abs() < 1e-6), "{p}");
// Expression via set_property.
let p = call_json(&mut s, "set_property", json!({"layer": l, "path": "transform/rotation", "expression": "time * 90"}));
assert_eq!(p["expression"], "time * 90");
+13 -1
View File
@@ -319,6 +319,9 @@ fn set_property(b: &mut Backend, a: &Value) -> Result<Reply> {
match time {
Some(t) => {
p["time"] = t.clone();
if let Some(tb) = get(a, "timeBase") {
p["timeBase"] = tb.clone();
}
b.exec("prop.addKey", p)?;
}
None => {
@@ -351,6 +354,9 @@ fn add_keyframe(b: &mut Backend, a: &Value) -> Result<Reply> {
if let Some(v) = get(k, "value") {
p["value"] = v.clone();
}
if let Some(tb) = get(a, "timeBase") {
p["timeBase"] = tb.clone();
}
b.exec("prop.addKey", p)?;
}
let info = b.exec("prop.get", obj(&base))?;
@@ -360,7 +366,11 @@ fn add_keyframe(b: &mut Backend, a: &Value) -> Result<Reply> {
b.exec("comp.open", json!({"comp": c}))?;
}
let sel: Vec<Value> = keys.iter().map(|k| json!({"layer": info["layer"], "prop": info["uid"], "time": k["time"]})).collect();
b.exec("keys.select", json!({"keys": sel}))?;
let mut select = json!({"keys": sel});
if let Some(tb) = get(a, "timeBase") {
select["timeBase"] = tb.clone();
}
b.exec("keys.select", select)?;
match interp.to_ascii_lowercase().as_str() {
"easyease" | "ease" => b.exec("keys.easyEase", json!({}))?,
"easyeasein" => b.exec("keys.easyEase", json!({"which": "in"}))?,
@@ -628,6 +638,7 @@ static TOOLS: &[ToolDef] = &[
json!({
"layer": layer_s(), "path": path_s(), "value": value_s(), "comp": comp_s(),
"time": {"type": "number", "description": "Keyframe time in seconds (layer time; equals comp time unless the layer is offset/stretched)."},
"timeBase": {"type": "string", "enum": ["layer", "comp"], "description": "What `time` counts: layer time (the default) or comp time, as the Timeline shows it."},
"expression": {"type": "string", "description": "Expression text; empty string removes the expression."}
}),
&["layer", "path"],
@@ -644,6 +655,7 @@ static TOOLS: &[ToolDef] = &[
json!({
"layer": layer_s(), "path": path_s(), "comp": comp_s(),
"time": {"type": "number", "description": "Key time in seconds (layer time)."},
"timeBase": {"type": "string", "enum": ["layer", "comp"], "description": "What `time` counts: layer time (the default) or comp time, as the Timeline shows it."},
"value": value_s(),
"keys": {"type": "array", "items": {"type": "object", "properties": {"time": {"type": "number"}, "value": value_s()}, "required": ["time"]}, "description": "Several keys at once."},
"interpolation": {"type": "string", "enum": ["linear", "bezier", "hold", "easyEase", "easyEaseIn", "easyEaseOut"]}