动作
每个窗口级动作都会成为一个名为 {winId}__{action} 的 MCP 工具。用 win.actions().add(name, title) 创建,按需细化,最后以 .on(handler) 收尾。
构建器选项
| 方法 | 作用 |
|---|---|
.description(s) | 在 tools/list 中给出可读的描述 |
.params::<T>() | 由 T: JsonSchema + DeserializeOwned 生成 inputSchema |
.returns::<R>() | 提供 outputSchema 中的 result 子结构 |
.read_only() .destructive() .idempotent() .open_world() | MCP 注解提示 |
.available(bool) | 在 tools/list 中的初始可见性 |
处理器的几种形式
.on(..) 接受六种形态。判断标准只有两个问题:需要参数吗? 以及 需要宿主上下文 &mut Cx 吗?
| 形式 | 运行于 | 适用于 |
|---|---|---|
on_ui(|| …) | 主线程 | 纯 UI 逻辑,无参数 |
on_ui(|p| …) | 主线程 | 带参数的纯 UI 逻辑 |
on_ui(|cx| …) | 主线程 | 需要操作真实控件,无参数 |
on_ui(|p, cx| …) | 主线程 | 需要操作真实控件,带参数 |
|| async { … } | tokio | 异步逻辑,无参数 |
|p| async { … } | tokio | 带参数的异步逻辑 |
rust
#[derive(Deserialize, JsonSchema)]
struct Add { text: String }
#[derive(Serialize, JsonSchema)]
struct Stats { total: usize, done: usize }
win.actions()
.add("add", "Add a todo")
.description("Append a new open item.")
.params::<Add>() // → inputSchema
.returns::<Stats>() // → outputSchema
.on(on_ui(move |p: Add, _cx: &mut Cx| {
let text = p.text.trim().to_string();
if text.is_empty() {
return Err(ActionError::field("text", "must not be empty"));
}
// …mutate real state, then let the read side mirror it…
Ok(Outcome::message("added").with(Stats { total: 1, done: 0 }))
}));返回结果
可以返回 &str、String 或 Outcome。Outcome::message(..).with(value) 会附带结构化数据,Agent 可以直接解析,而不必重新读取整份快照。
异步处理器与主线程
重要
async 处理器运行在 tokio 上,因此不会收到 &mut Cx。若要在 await 之后操作 UI,请捕获一个 Ui<Cx>(来自 app.ui())再切回主线程:
rust
let ui = app.ui();
win.actions().add("report", "Build a report").on(move || {
let ui = ui.clone();
async move {
let data = fetch_from_network().await; // off-thread work
let n = ui.run(move |cx| apply(cx, data)).await; // back on the main thread
Ok::<_, ActionError>(Outcome::message(format!("{n} rows")))
}
});在运行时改变动作
.on(..) 会返回一个 ActionHandle。保留它,就能隐藏当前不该被 Agent 调用的工具——工具列表会被重新计算,并通知各客户端:
rust
let clear = win.actions().add("clear_done", "Clear completed")
.available(false) // hidden until useful
.on(on_ui(|| Ok::<_, ActionError>("cleared")));
// In sync(), as state changes:
clear.set_available(self.items.iter().any(|t| t.done));ActionHandle 还提供 set_title、set_description 和 remove。
这么做值得吗
如果 Agent 看到的永远只有当前可调用的工具,它就不需要猜,也不会把一轮对话浪费在注定失败的动作上。