Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"core:event:default",
"core:event:allow-listen",
"core:event:allow-emit",
"allow-audit-logs"
"allow-audit-logs",
"allow-mcp-folder-picker"
]
}
4 changes: 4 additions & 0 deletions src-tauri/permissions/mcp-folder-picker.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[permission]]
identifier = "allow-mcp-folder-picker"
description = "Open the native folder picker for MCP filesystem allow-list paths."
commands.allow = ["pick_mcp_filesystem_folder"]
16 changes: 13 additions & 3 deletions src-tauri/src/modules/bot/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,24 @@ pub async fn disconnect_bot(state: tauri::State<'_, AppState>) -> Result<String,
}

/// Native folder picker for MCP filesystem allow-list (desktop).
///
/// Uses the non-blocking callback API with a oneshot channel so the dialog
/// dispatch to the main thread doesn't deadlock against the async runtime
/// that's awaiting the result (a known issue with `blocking_pick_folder`
/// inside `async` Tauri commands on macOS).
#[cfg(desktop)]
#[tauri::command]
pub async fn pick_mcp_filesystem_folder(app: tauri::AppHandle) -> Result<Option<String>, String> {
let folder = app
.dialog()
let (tx, rx) = tokio::sync::oneshot::channel();
app.dialog()
.file()
.set_title("Folder for MCP filesystem tools")
.blocking_pick_folder();
.pick_folder(move |folder| {
let _ = tx.send(folder);
});
let folder = rx
.await
.map_err(|e| format!("folder picker closed unexpectedly: {e}"))?;
Ok(folder.map(|p| p.to_string()))
}

Expand Down
20 changes: 11 additions & 9 deletions src/modules/mcp/components/McpServerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ type Props = {
onReloadServers?: () => Promise<void>;
};

/** Tauri `invoke` rejects with a plain string for command `Err(...)` returns,
* not an `Error` — surface that string instead of a generic fallback. */
function pickFolderErrorMessage(e: unknown): string {
if (e instanceof Error) return e.message;
if (typeof e === "string" && e.trim()) return e;
return "Could not open folder picker";
}

/** Detect filesystem MCP package in live args textarea (one token per line). */
function argsTextLooksLikeFilesystem(argsText: string): boolean {
return argsText
Expand Down Expand Up @@ -329,9 +337,7 @@ function InlineEditForm({
const picked = await invoke<string | null>("pick_mcp_filesystem_folder");
if (picked) addPath(picked);
} catch (invokeErr) {
setPickFolderError(
invokeErr instanceof Error ? invokeErr.message : "Could not open folder picker",
);
setPickFolderError(pickFolderErrorMessage(invokeErr));
}
} catch {
// Web / non-Tauri: dynamic import of `@tauri-apps/api/core` fails — expected, stay silent
Expand All @@ -356,9 +362,7 @@ function InlineEditForm({
const picked = await invoke<string | null>("pick_mcp_filesystem_folder");
if (picked) addTePath(picked);
} catch (invokeErr) {
setTePickError(
invokeErr instanceof Error ? invokeErr.message : "Could not open folder picker",
);
setTePickError(pickFolderErrorMessage(invokeErr));
}
} catch {
// Web / non-Tauri
Expand Down Expand Up @@ -386,9 +390,7 @@ function InlineEditForm({
const picked = await invoke<string | null>("pick_mcp_filesystem_folder");
if (picked) setTePrivatePathInput(picked);
} catch (invokeErr) {
setTePrivatePickError(
invokeErr instanceof Error ? invokeErr.message : "Could not open folder picker",
);
setTePrivatePickError(pickFolderErrorMessage(invokeErr));
}
} catch {
setTePrivatePickError("Folder picker needs the desktop app (Tauri).");
Expand Down
14 changes: 7 additions & 7 deletions tools/skills/weather/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ Chat, not a datasheet: short intro on how it _feels_, then compact day lines (da

## wttr.in (curl / fetch)

| Goal | Example |
| ---- | ------- |
| Short (default), 1–2 days | `curl -s "wttr.in/London?2&m"` |
| Full week-style table | `curl -s "wttr.in/London?T&m"` |
| One line now | `curl -s "wttr.in/London?format=3"` |
| Custom one-liner | `curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"` |
| Goal | Example |
| ------------------------- | ------------------------------------------------- |
| Short (default), 1–2 days | `curl -s "wttr.in/London?2&m"` |
| Full week-style table | `curl -s "wttr.in/London?T&m"` |
| One line now | `curl -s "wttr.in/London?format=3"` |
| Custom one-liner | `curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"` |

Tips: `+` for spaces; `?m` / `?u` units; `?1` today only; `?0` now only; airports `wttr.in/JFK`; PNG `wttr.in/Berlin.png`.

Expand All @@ -41,7 +41,7 @@ Tips: `+` for spaces; `?m` / `?u` units; `?1` today only; `?0` now only; airport
**Flow (max 2 geocode + 1 forecast):**

1. `https://geocoding-api.open-meteo.com/v1/search?name=PLACE&count=10` (spaces as `+`)
2. If `results` missing/empty: **one** retry — shorter `name` + `countryCode` (e.g. `name=Breitenau&countryCode=AT` for _Breitenau am Hochlantsch_). Pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase.
2. If `results` missing/empty: **one** retry — shorter `name` + `countryCode` Pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase.
3. `https://api.open-meteo.com/v1/forecast?latitude=LAT&longitude=LON&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode&forecast_days=7&timezone=auto` — extend `forecast_days` to 7–16 if they asked for more days.

Current only: `curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12&current_weather=true"`
Expand Down
2 changes: 1 addition & 1 deletion tools/skills/weather/mandatory.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
**MANDATORY for skill:weather:** **One fetch first:** prefer `https://wttr.in/PLACE?2&m` (spaces → `+`, e.g. `Breitenau+am+Hochlantsch`) for tomorrow/today-style questions — smaller/faster than the full table. Use `https://wttr.in/PLACE?T&m` only if the user wants a longer range or `?2` is insufficient. If the body is a normal forecast, **answer and stop** — do not call Open-Meteo in the same run. **Open-Meteo** only if wttr fails or raw JSON is needed: (1) `https://geocoding-api.open-meteo.com/v1/search?name=…&count=10`; (2) if `results` is missing or empty, **one** retry with shorter `name` + `countryCode` (e.g. `https://geocoding-api.open-meteo.com/v1/search?name=Breitenau&countryCode=AT&count=10` for “Breitenau am Hochlantsch”) and pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase; (3) `https://api.open-meteo.com/v1/forecast?latitude=…&longitude=…&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode&forecast_days=7&timezone=auto`. Never claim the place was not found after a single geocode on compound EU names. Reply per **How to answer**: plain language; no coordinates or WMO codes unless the user asked for technical detail.
**MANDATORY for skill:weather:** **One fetch first:** prefer `https://wttr.in/PLACE?2&m` for tomorrow/today-style questions — smaller/faster than the full table. Use `https://wttr.in/PLACE?T&m` only if the user wants a longer range or `?2` is insufficient. If the body is a normal forecast, **answer and stop** — do not call Open-Meteo in the same run. **Open-Meteo** only if wttr fails or raw JSON is needed: (1) `https://geocoding-api.open-meteo.com/v1/search?name=…&count=10`; (2) if `results` is missing or empty, **one** retry with shorter `name` + `countryCode` (e.g. `https://geocoding-api.open-meteo.com/v1/search?name=Breitenau&countryCode=AT&count=10` for “Breitenau am Hochlantsch”) and pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase; (3) `https://api.open-meteo.com/v1/forecast?latitude=…&longitude=…&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode&forecast_days=7&timezone=auto`. Never claim the place was not found after a single geocode on compound EU names. Reply per **How to answer**: plain language; no coordinates or WMO codes unless the user asked for technical detail.