Skip to content

动作

每个窗口级动作都会成为一个名为 {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 }))
    }));

返回结果

可以返回 &strStringOutcomeOutcome::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_titleset_descriptionremove

这么做值得吗

如果 Agent 看到的永远只有当前可调用的工具,它就不需要猜,也不会把一轮对话浪费在注定失败的动作上。