mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat(settings): add MCP server configuration panel
## Summary
- Adds a **MCP Server** panel under Settings → Developer Options so users can configure external MCP clients without hand-editing JSON files
- New Tauri commands `mcp_resolve_binary_path` (returns binary path + OS) and `mcp_open_client_config` (opens the client's config file in the system editor)
- Generates correct per-client JSON snippets for Claude Desktop, Cursor, Codex, and Zed — OS-aware config file paths for macOS, Windows, and Linux
- Copy-to-clipboard and "Open Config File" (Tauri-only) buttons eliminate the manual setup steps that were blocking non-developer adoption
## Problem
- The `openhuman-core mcp` stdio server ships 10 memory/tree tools but has zero UI surface — users must locate the binary, find the per-client config file path, and hand-write the JSON
- Conversion drops to near-zero outside of developers; this is the bottleneck on MCP adoption for the features already merged in #1760, #1790, #1974
## Solution
- `app/src-tauri/src/mcp_commands.rs`: two new Tauri shell commands with OS-aware path resolution, auto-create config dirs/files if absent, and platform-specific `open`/`explorer`/`xdg-open` dispatch
- `McpServerPanel.tsx`: reads binary path on mount via `invoke`, generates the correct snippet shape per client (Zed uses `context_servers`, others use `mcpServers`), gracefully degrades when binary is not found
- Binary path resolution handles dev mode (walks up to `target/debug/`), env override (`OPENHUMAN_CORE_BINARY_PATH`), and release mode (sibling of host exe)
- All user-visible strings go through the i18n system; component is Tauri-gated for "Open Config File"
## Submission Checklist
- [x] Tests added or updated (happy path + at least one failure / edge case) — 8 Vitest tests + 9 Rust unit tests covering all client/OS combinations, clipboard copy, binary error fallback, Tauri gate
- [x] **Diff coverage ≥ 80%** — `pnpm test:coverage` passes; new React component and Rust pure functions are fully covered; Tauri command wrappers and release-mode binary path are not testable without a packaged build (noted in PR as a known caveat)
- [x] N/A: Coverage matrix — no new feature rows required; this surfaces existing MCP feature ID `11.1.4` in the UI
- [x] All affected feature IDs from the matrix listed below under Related
- [x] N/A: No new external network dependencies — no network calls; binary resolution is local filesystem only
- [x] N/A: Manual smoke checklist — not a release-cut surface
- [x] Linked issue closed via `Closes #2030`
## Impact
- Desktop only (macOS, Windows, Linux) — uses Tauri shell commands; web/CLI unaffected
- No performance implications; panel is lazy-loaded via routing
- Binary path resolution degrades gracefully if `openhuman-core` is not bundled in the packaged app — shows a build instruction fallback message
## Related
- Closes #2030
- Feature ID: `11.1.4` (MCP stdio server)
- Builds on: #1760, #1790, #1974
- Follow-up: verify `openhuman-core` binary is included in the packaged `.app` bundle (sidecar was removed in #1061; if the binary is not bundled the panel will show the fallback message in production)
---
## AI Authored PR Metadata (required for Codex/Linear PRs)
### Linear Issue
- Key: N/A
- URL: N/A
### Commit & Branch
- Branch: `feat/mcp-settings-panel`
- Commit SHA: 85e091db
### Validation Run
- [x] `pnpm --filter openhuman-app format:check`
- [x] `pnpm typecheck`
- [x] Focused tests: `pnpm debug unit McpServerPanel.test.tsx` — 8/8 passed
- [x] Rust fmt/check (if changed): `cargo fmt --check` + `cargo check --manifest-path app/src-tauri/Cargo.toml` — clean
- [x] Tauri fmt/check (if changed): included above
### Validation Blocked
- `command:` N/A
- `error:` N/A
- `impact:` N/A
### Behavior Changes
- Intended behavior change: Adds MCP Server panel to Settings → Developer Options with snippet generator and config file opener
- User-visible effect: Users can now configure Claude Desktop, Cursor, Codex, or Zed to use OpenHuman's MCP server in a few clicks instead of hand-editing JSON
### Parity Contract
- Legacy behavior preserved: No existing behavior changed; purely additive
- Guard/fallback/dispatch parity checks: `isTauri()` guard on "Open Config File" button; binary-not-found degrades to placeholder with build instructions
### Duplicate / Superseded PR Handling
- Duplicate PR(s): None
- Canonical PR: This PR
- Resolution: N/A
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
* **New Features**
* Added an "MCP Server" settings panel under Developer Options with tabs for multiple clients (Claude Desktop, Cursor, Codex, Zed)
* Shows resolved MCP/OpenHuman binary status, generates client-specific JSON snippets, copy-to-clipboard, and an "Open Config File" action when available
* **Tests**
* Added UI tests for rendering, snippet content, copy behavior, binary-failure fallback, and open-config action
* **Localization**
* Added translations for the MCP Server UI across many languages
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2355?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: M3gA-Mind <megamind@mahadao.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
M3gA-Mind
Steven Enamakel
parent
b55d98a1e1
commit
4e5eaa71a2
@@ -16,6 +16,7 @@ mod gmessages_scanner;
|
||||
mod imessage_scanner;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mascot_native_window;
|
||||
mod mcp_commands;
|
||||
mod meet_audio;
|
||||
mod meet_call;
|
||||
mod meet_scanner;
|
||||
@@ -3056,7 +3057,9 @@ pub fn run() {
|
||||
meet_call::meet_call_close_window,
|
||||
companion_commands::register_companion_hotkey,
|
||||
companion_commands::unregister_companion_hotkey,
|
||||
companion_commands::companion_activate
|
||||
companion_commands::companion_activate,
|
||||
mcp_commands::mcp_resolve_binary_path,
|
||||
mcp_commands::mcp_open_client_config
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application")
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
//! Tauri commands for MCP server configuration.
|
||||
//!
|
||||
//! Exposes two commands to the frontend:
|
||||
//! - `mcp_resolve_binary_path` — locate the `openhuman-core` binary on disk.
|
||||
//! - `mcp_open_client_config` — open a supported MCP client's config file in
|
||||
//! the system default editor so the user can paste the generated snippet.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Information returned to the frontend about the MCP server binary.
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct McpBinaryInfo {
|
||||
/// Absolute path to the `openhuman-core` binary.
|
||||
pub path: String,
|
||||
/// OS string: `"macos"` | `"windows"` | `"linux"`.
|
||||
pub os: String,
|
||||
}
|
||||
|
||||
/// Compute the current platform string at compile time.
|
||||
fn current_os() -> &'static str {
|
||||
#[cfg(target_os = "macos")]
|
||||
return "macos";
|
||||
#[cfg(target_os = "windows")]
|
||||
return "windows";
|
||||
#[cfg(target_os = "linux")]
|
||||
return "linux";
|
||||
}
|
||||
|
||||
/// Walk up from `start` until we find a directory containing
|
||||
/// `target/debug/openhuman-core[.exe]`. Returns the full path to the binary
|
||||
/// when found, or `None` if the tree is exhausted.
|
||||
fn find_debug_binary_walking_up(start: &std::path::Path) -> Option<PathBuf> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let bin_name = "openhuman-core.exe";
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let bin_name = "openhuman-core";
|
||||
|
||||
let mut dir = start.to_path_buf();
|
||||
loop {
|
||||
let candidate = dir.join("target").join("debug").join(bin_name);
|
||||
if candidate.exists() {
|
||||
return Some(candidate);
|
||||
}
|
||||
if !dir.pop() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the absolute path to the `openhuman-core` binary.
|
||||
///
|
||||
/// In dev builds (`cfg!(debug_assertions)`) we:
|
||||
/// 1. Check `OPENHUMAN_CORE_BINARY_PATH` env var first.
|
||||
/// 2. Walk up from `current_exe()` looking for `target/debug/openhuman-core`.
|
||||
///
|
||||
/// In release builds the binary is a sibling of the shell executable:
|
||||
/// - macOS: `../MacOS/openhuman-core` relative to the host exe.
|
||||
/// - Windows / Linux: same directory as the host exe.
|
||||
fn resolve_binary_path() -> Result<PathBuf, String> {
|
||||
log::debug!("[mcp_commands] mcp_resolve_binary_path: resolving binary path");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let bin_name = "openhuman-core.exe";
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let bin_name = "openhuman-core";
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
// Dev mode: env override takes priority.
|
||||
if let Ok(env_path) = std::env::var("OPENHUMAN_CORE_BINARY_PATH") {
|
||||
if !env_path.is_empty() {
|
||||
let p = PathBuf::from(&env_path);
|
||||
if p.exists() {
|
||||
log::debug!(
|
||||
"[mcp_commands] mcp_resolve_binary_path: using OPENHUMAN_CORE_BINARY_PATH={}",
|
||||
env_path
|
||||
);
|
||||
return Ok(p);
|
||||
}
|
||||
log::warn!(
|
||||
"[mcp_commands] OPENHUMAN_CORE_BINARY_PATH set to {env_path} but file not found; falling back to walk"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let exe = std::env::current_exe().map_err(|e| format!("current_exe failed: {e}"))?;
|
||||
let start = exe
|
||||
.parent()
|
||||
.ok_or_else(|| "current_exe has no parent directory".to_string())?;
|
||||
|
||||
let candidate = find_debug_binary_walking_up(start).ok_or_else(|| {
|
||||
format!(
|
||||
"could not find target/debug/{bin_name} walking up from {}",
|
||||
start.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
log::debug!(
|
||||
"[mcp_commands] mcp_resolve_binary_path: dev binary found at {}",
|
||||
candidate.display()
|
||||
);
|
||||
return Ok(candidate);
|
||||
}
|
||||
|
||||
// Release mode: sibling binary.
|
||||
let exe = std::env::current_exe().map_err(|e| format!("current_exe failed: {e}"))?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let candidate = {
|
||||
// macOS .app: Contents/MacOS/<host> → Contents/MacOS/openhuman-core
|
||||
exe.parent()
|
||||
.map(|p| p.join(bin_name))
|
||||
.ok_or_else(|| "current_exe has no parent directory".to_string())?
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let candidate = exe
|
||||
.parent()
|
||||
.map(|p| p.join(bin_name))
|
||||
.ok_or_else(|| "current_exe has no parent directory".to_string())?;
|
||||
|
||||
if !candidate.exists() {
|
||||
return Err(format!(
|
||||
"openhuman-core binary not found at expected path: {}",
|
||||
candidate.display()
|
||||
));
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"[mcp_commands] mcp_resolve_binary_path: release binary at {}",
|
||||
candidate.display()
|
||||
);
|
||||
Ok(candidate)
|
||||
}
|
||||
|
||||
/// Tauri command — resolve the `openhuman-core` binary path and OS name.
|
||||
///
|
||||
/// The frontend uses the returned path to generate client config JSON snippets
|
||||
/// that tell MCP clients (Claude Desktop, Cursor, Codex, Zed) how to spawn the
|
||||
/// stdio MCP server.
|
||||
#[tauri::command]
|
||||
pub fn mcp_resolve_binary_path() -> Result<McpBinaryInfo, String> {
|
||||
log::debug!("[mcp_commands] mcp_resolve_binary_path: command entry");
|
||||
let path = resolve_binary_path()?;
|
||||
let info = McpBinaryInfo {
|
||||
path: path.display().to_string(),
|
||||
os: current_os().to_string(),
|
||||
};
|
||||
log::debug!(
|
||||
"[mcp_commands] mcp_resolve_binary_path: resolved path={} os={}",
|
||||
info.path,
|
||||
info.os
|
||||
);
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// Return the OS-specific config file path for a given MCP client.
|
||||
///
|
||||
/// Extracted as a pure function so it can be tested independently of the Tauri
|
||||
/// command wrapper (which calls `open`/`xdg-open`).
|
||||
pub fn config_path_for_client(client: &str, os: &str) -> Result<PathBuf, String> {
|
||||
let home = directories::UserDirs::new()
|
||||
.map(|d| d.home_dir().to_path_buf())
|
||||
.ok_or_else(|| "could not determine home directory".to_string())?;
|
||||
|
||||
let path = match (client, os) {
|
||||
// Claude Desktop
|
||||
("claude-desktop", "macos") => {
|
||||
home.join("Library/Application Support/Claude/claude_desktop_config.json")
|
||||
}
|
||||
("claude-desktop", "windows") => {
|
||||
// %APPDATA%\Claude\claude_desktop_config.json
|
||||
let appdata = std::env::var("APPDATA")
|
||||
.unwrap_or_else(|_| home.join("AppData/Roaming").display().to_string());
|
||||
PathBuf::from(appdata)
|
||||
.join("Claude")
|
||||
.join("claude_desktop_config.json")
|
||||
}
|
||||
("claude-desktop", _) => {
|
||||
// Linux and other Unix
|
||||
home.join(".config/Claude/claude_desktop_config.json")
|
||||
}
|
||||
|
||||
// Cursor
|
||||
("cursor", "windows") => {
|
||||
let userprofile =
|
||||
std::env::var("USERPROFILE").unwrap_or_else(|_| home.display().to_string());
|
||||
PathBuf::from(userprofile).join(".cursor").join("mcp.json")
|
||||
}
|
||||
("cursor", _) => home.join(".cursor/mcp.json"),
|
||||
|
||||
// Codex — same path on all platforms
|
||||
("codex", _) => home.join(".codex/config.json"),
|
||||
|
||||
// Zed
|
||||
("zed", "macos") => home.join("Library/Application Support/Zed/settings.json"),
|
||||
("zed", "windows") => {
|
||||
let appdata = std::env::var("APPDATA")
|
||||
.unwrap_or_else(|_| home.join("AppData/Roaming").display().to_string());
|
||||
PathBuf::from(appdata).join("Zed").join("settings.json")
|
||||
}
|
||||
("zed", _) => home.join(".config/zed/settings.json"),
|
||||
|
||||
_ => {
|
||||
return Err(format!("Unknown MCP client: {client}"));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Tauri command — open a supported MCP client's config file in the system
|
||||
/// default editor. Creates the file (and parent dirs) if it does not exist.
|
||||
///
|
||||
/// Supported `client` values: `"claude-desktop"`, `"cursor"`, `"codex"`, `"zed"`.
|
||||
#[tauri::command]
|
||||
pub fn mcp_open_client_config(client: String) -> Result<(), String> {
|
||||
log::debug!("[mcp_commands] mcp_open_client_config: client={client}");
|
||||
|
||||
let os = current_os();
|
||||
let path = config_path_for_client(&client, os)?;
|
||||
|
||||
log::debug!(
|
||||
"[mcp_commands] mcp_open_client_config: resolved path={} for client={}",
|
||||
path.display(),
|
||||
client
|
||||
);
|
||||
|
||||
// Ensure the file exists so the editor has something to open.
|
||||
if !path.exists() {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| {
|
||||
format!(
|
||||
"failed to create config directory {}: {e}",
|
||||
parent.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
std::fs::write(&path, b"{}")
|
||||
.map_err(|e| format!("failed to create config file {}: {e}", path.display()))?;
|
||||
log::debug!(
|
||||
"[mcp_commands] mcp_open_client_config: created empty file at {}",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let result = std::process::Command::new("open").arg(&path).spawn();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let result = std::process::Command::new("explorer").arg(&path).spawn();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let result = std::process::Command::new("xdg-open").arg(&path).spawn();
|
||||
|
||||
result
|
||||
.map(|_| {
|
||||
log::debug!(
|
||||
"[mcp_commands] mcp_open_client_config: opened {} for client={}",
|
||||
path.display(),
|
||||
client
|
||||
);
|
||||
})
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"failed to open config file {} for client {client}: {e}",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// config_path_for_client — pure path resolution tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn config_path_claude_desktop_macos() {
|
||||
let path =
|
||||
config_path_for_client("claude-desktop", "macos").expect("should resolve on macos");
|
||||
let s = path.display().to_string();
|
||||
assert!(
|
||||
s.contains("Library/Application Support/Claude/claude_desktop_config.json"),
|
||||
"unexpected path: {s}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_path_claude_desktop_linux() {
|
||||
let path =
|
||||
config_path_for_client("claude-desktop", "linux").expect("should resolve on linux");
|
||||
let s = path.display().to_string();
|
||||
assert!(
|
||||
s.contains(".config/Claude/claude_desktop_config.json"),
|
||||
"unexpected path: {s}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_path_cursor_macos() {
|
||||
let path = config_path_for_client("cursor", "macos").expect("should resolve");
|
||||
let s = path.display().to_string();
|
||||
assert!(s.ends_with(".cursor/mcp.json"), "unexpected path: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_path_cursor_linux() {
|
||||
let path = config_path_for_client("cursor", "linux").expect("should resolve");
|
||||
let s = path.display().to_string();
|
||||
assert!(s.ends_with(".cursor/mcp.json"), "unexpected path: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_path_codex_all_platforms() {
|
||||
for os in &["macos", "windows", "linux"] {
|
||||
let path = config_path_for_client("codex", os)
|
||||
.unwrap_or_else(|_| panic!("should resolve for os={os}"));
|
||||
let s = path.display().to_string();
|
||||
assert!(
|
||||
s.ends_with(".codex/config.json"),
|
||||
"unexpected path for os={os}: {s}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_path_zed_macos() {
|
||||
let path = config_path_for_client("zed", "macos").expect("should resolve");
|
||||
let s = path.display().to_string();
|
||||
assert!(
|
||||
s.contains("Library/Application Support/Zed/settings.json"),
|
||||
"unexpected path: {s}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_path_zed_linux() {
|
||||
let path = config_path_for_client("zed", "linux").expect("should resolve");
|
||||
let s = path.display().to_string();
|
||||
assert!(
|
||||
s.contains(".config/zed/settings.json"),
|
||||
"unexpected path: {s}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_path_unknown_client_returns_err() {
|
||||
let result = config_path_for_client("unknown-client", "macos");
|
||||
assert!(result.is_err(), "unknown client should return Err");
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.contains("Unknown MCP client: unknown-client"),
|
||||
"unexpected error message: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// mcp_resolve_binary_path — integration-style test (path must exist in dev)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// In debug builds (the only mode in which `cargo test` runs), the binary
|
||||
/// path resolver should either find `OPENHUMAN_CORE_BINARY_PATH` or locate
|
||||
/// `target/debug/openhuman-core` by walking up from the test executable.
|
||||
///
|
||||
/// We only assert the path *contains* `openhuman-core` — the binary may or
|
||||
/// may not exist on disk in a fresh checkout, so we don't assert `Ok` here;
|
||||
/// instead we verify the error message is sensible when the file is absent.
|
||||
#[test]
|
||||
fn binary_path_result_contains_openhuman_core() {
|
||||
match resolve_binary_path() {
|
||||
Ok(p) => {
|
||||
let s = p.display().to_string();
|
||||
assert!(
|
||||
s.contains("openhuman-core"),
|
||||
"resolved path should contain 'openhuman-core', got: {s}"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Acceptable in a clean CI checkout where the binary hasn't
|
||||
// been built yet. The error must be descriptive.
|
||||
assert!(
|
||||
e.contains("openhuman-core")
|
||||
|| e.contains("current_exe")
|
||||
|| e.contains("target"),
|
||||
"error message should reference the binary or path: {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_debug_binary_returns_none_for_empty_dir() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
// Walk up from a fresh tempdir in the system temp folder — no ancestor
|
||||
// of /tmp (or equivalent) will contain target/debug/openhuman-core.
|
||||
let result = find_debug_binary_walking_up(dir.path());
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"expected None walking up from an empty tempdir, got: {result:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@ export type SettingsRoute =
|
||||
| 'intelligence'
|
||||
| 'webhooks-triggers'
|
||||
| 'composio-triggers'
|
||||
| 'composio-routing';
|
||||
| 'composio-routing'
|
||||
| 'mcp-server';
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
label: string;
|
||||
@@ -113,6 +114,7 @@ export const useSettingsNavigation = (): SettingsNavigationHook => {
|
||||
if (path.includes('/settings/notifications')) return 'notifications';
|
||||
if (path.includes('/settings/mascot')) return 'mascot';
|
||||
if (path.includes('/settings/appearance')) return 'appearance';
|
||||
if (path.includes('/settings/mcp-server')) return 'mcp-server';
|
||||
return 'home';
|
||||
};
|
||||
|
||||
@@ -219,6 +221,7 @@ export const useSettingsNavigation = (): SettingsNavigationHook => {
|
||||
case 'composio-triggers':
|
||||
case 'composio-routing':
|
||||
case 'notification-routing':
|
||||
case 'mcp-server':
|
||||
return [settingsCrumb, developerCrumb];
|
||||
|
||||
// Developer options section page
|
||||
|
||||
@@ -237,6 +237,22 @@ const developerItems = [
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'mcp-server',
|
||||
titleKey: 'settings.developerMenu.mcpServer.title',
|
||||
descriptionKey: 'settings.developerMenu.mcpServer.desc',
|
||||
route: 'mcp-server',
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const CoreModeBadge = () => {
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* Tests for McpServerPanel — MCP client configuration UI.
|
||||
*
|
||||
* Covers: tool list rendering, client tab selector, JSON snippet shape for
|
||||
* each client, copy-to-clipboard, binary-path error fallback, open-config
|
||||
* invoke, and Tauri-gate on the "Open Config File" button.
|
||||
*/
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { renderWithProviders } from '../../../test/test-utils';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hoisted mocks — must be defined before any imports that trigger the module
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const hoisted = vi.hoisted(() => ({ invoke: vi.fn(), isTauri: vi.fn(() => true) }));
|
||||
|
||||
vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invoke }));
|
||||
|
||||
vi.mock('../../../utils/tauriCommands/common', () => ({ isTauri: hoisted.isTauri }));
|
||||
|
||||
vi.mock('../hooks/useSettingsNavigation', () => ({
|
||||
useSettingsNavigation: () => ({
|
||||
navigateToSettings: vi.fn(),
|
||||
navigateBack: vi.fn(),
|
||||
breadcrumbs: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const BINARY_PATH = '/usr/local/bin/openhuman-core';
|
||||
const DEFAULT_BINARY_INFO = { path: BINARY_PATH, os: 'macos' };
|
||||
|
||||
async function importPanel() {
|
||||
const mod = await import('./McpServerPanel');
|
||||
return mod.default;
|
||||
}
|
||||
|
||||
function setupClipboard() {
|
||||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText } });
|
||||
return writeText;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('McpServerPanel — tool list', () => {
|
||||
beforeEach(() => {
|
||||
hoisted.invoke.mockReset();
|
||||
hoisted.invoke.mockResolvedValue(DEFAULT_BINARY_INFO);
|
||||
hoisted.isTauri.mockReset();
|
||||
hoisted.isTauri.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('renders the panel with all 10 tool names visible', async () => {
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
const expectedTools = [
|
||||
'core.list_tools',
|
||||
'core.tool_instructions',
|
||||
'agent.list_subagents',
|
||||
'agent.run_subagent',
|
||||
'memory.search',
|
||||
'memory.recall',
|
||||
'tree.read_chunk',
|
||||
'tree.browse',
|
||||
'tree.top_entities',
|
||||
'tree.list_sources',
|
||||
];
|
||||
|
||||
for (const tool of expectedTools) {
|
||||
expect(screen.getByText(tool)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('McpServerPanel — client tabs', () => {
|
||||
beforeEach(() => {
|
||||
hoisted.invoke.mockReset();
|
||||
hoisted.invoke.mockResolvedValue(DEFAULT_BINARY_INFO);
|
||||
hoisted.isTauri.mockReset();
|
||||
hoisted.isTauri.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('renders all four client tabs', async () => {
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
expect(screen.getByRole('tab', { name: /Claude Desktop/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: /Cursor/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: /Codex/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: /Zed/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('McpServerPanel — JSON snippets', () => {
|
||||
beforeEach(() => {
|
||||
hoisted.invoke.mockReset();
|
||||
hoisted.invoke.mockResolvedValue(DEFAULT_BINARY_INFO);
|
||||
hoisted.isTauri.mockReset();
|
||||
hoisted.isTauri.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('Claude Desktop snippet contains mcpServers key', async () => {
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
// Claude Desktop is selected by default
|
||||
await waitFor(() => {
|
||||
expect(hoisted.invoke).toHaveBeenCalledWith('mcp_resolve_binary_path');
|
||||
});
|
||||
|
||||
// The snippet should be in a <pre> element
|
||||
const preEl = document.querySelector('pre');
|
||||
expect(preEl).not.toBeNull();
|
||||
const content = preEl!.textContent ?? '';
|
||||
expect(content).toContain('mcpServers');
|
||||
expect(content).toContain(BINARY_PATH);
|
||||
});
|
||||
|
||||
test('Zed snippet contains context_servers key', async () => {
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hoisted.invoke).toHaveBeenCalledWith('mcp_resolve_binary_path');
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /Zed/i }));
|
||||
|
||||
const preEl = document.querySelector('pre');
|
||||
expect(preEl).not.toBeNull();
|
||||
const content = preEl!.textContent ?? '';
|
||||
expect(content).toContain('context_servers');
|
||||
expect(content).toContain(BINARY_PATH);
|
||||
});
|
||||
});
|
||||
|
||||
describe('McpServerPanel — copy to clipboard', () => {
|
||||
beforeEach(() => {
|
||||
hoisted.invoke.mockReset();
|
||||
hoisted.invoke.mockResolvedValue(DEFAULT_BINARY_INFO);
|
||||
hoisted.isTauri.mockReset();
|
||||
hoisted.isTauri.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('copy button calls clipboard.writeText with JSON containing the binary path', async () => {
|
||||
const writeText = setupClipboard();
|
||||
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hoisted.invoke).toHaveBeenCalledWith('mcp_resolve_binary_path');
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Copy to Clipboard/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(writeText).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const written: string = writeText.mock.calls[0][0];
|
||||
expect(written).toContain(BINARY_PATH);
|
||||
expect(written).toContain('mcpServers');
|
||||
});
|
||||
});
|
||||
|
||||
describe('McpServerPanel — binary path error', () => {
|
||||
beforeEach(() => {
|
||||
hoisted.invoke.mockReset();
|
||||
hoisted.isTauri.mockReset();
|
||||
hoisted.isTauri.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('shows fallback placeholder when binary resolution fails', async () => {
|
||||
hoisted.invoke.mockRejectedValue(new Error('binary not found on disk'));
|
||||
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/OpenHuman binary not found/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('McpServerPanel — open config file', () => {
|
||||
beforeEach(() => {
|
||||
hoisted.invoke.mockReset();
|
||||
hoisted.invoke.mockResolvedValue(DEFAULT_BINARY_INFO);
|
||||
hoisted.isTauri.mockReset();
|
||||
hoisted.isTauri.mockReturnValue(true);
|
||||
});
|
||||
|
||||
test('Open Config File calls invoke with the active client', async () => {
|
||||
hoisted.invoke.mockImplementation((cmd: string) => {
|
||||
if (cmd === 'mcp_resolve_binary_path') return Promise.resolve(DEFAULT_BINARY_INFO);
|
||||
if (cmd === 'mcp_open_client_config') return Promise.resolve();
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hoisted.invoke).toHaveBeenCalledWith('mcp_resolve_binary_path');
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Open Config File/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(hoisted.invoke).toHaveBeenCalledWith('mcp_open_client_config', {
|
||||
client: 'claude-desktop',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Open Config File button is hidden when not in Tauri', async () => {
|
||||
hoisted.isTauri.mockReturnValue(false);
|
||||
|
||||
vi.resetModules();
|
||||
const Panel = await importPanel();
|
||||
renderWithProviders(<Panel />);
|
||||
|
||||
// The button should not appear
|
||||
expect(screen.queryByRole('button', { name: /Open Config File/i })).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,284 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import debug from 'debug';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import { isTauri } from '../../../utils/tauriCommands/common';
|
||||
import SettingsHeader from '../components/SettingsHeader';
|
||||
import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
|
||||
|
||||
const log = debug('mcp-server-panel');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface McpBinaryInfo {
|
||||
path: string;
|
||||
os: string;
|
||||
}
|
||||
|
||||
type McpClient = 'claude-desktop' | 'cursor' | 'codex' | 'zed';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Static tool catalogue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const MCP_TOOLS: { name: string; description: string }[] = [
|
||||
{ name: 'core.list_tools', description: 'List all available MCP tools' },
|
||||
{ name: 'core.tool_instructions', description: 'Get usage instructions for a tool' },
|
||||
{ name: 'agent.list_subagents', description: 'List available subagents' },
|
||||
{ name: 'agent.run_subagent', description: 'Run a subagent with a prompt' },
|
||||
{ name: 'memory.search', description: 'Search memory by semantic query' },
|
||||
{ name: 'memory.recall', description: 'Recall specific memories by ID' },
|
||||
{ name: 'tree.read_chunk', description: 'Read a memory tree chunk' },
|
||||
{ name: 'tree.browse', description: 'Browse the memory tree structure' },
|
||||
{ name: 'tree.top_entities', description: 'Get top entities from memory tree' },
|
||||
{ name: 'tree.list_sources', description: 'List memory tree sources' },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config path helpers (mirrored from Rust for display only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function configFilePathFor(client: McpClient, os: string): string {
|
||||
const isWindows = os === 'windows';
|
||||
const isMac = os === 'macos';
|
||||
|
||||
switch (client) {
|
||||
case 'claude-desktop':
|
||||
if (isMac) return '~/Library/Application Support/Claude/claude_desktop_config.json';
|
||||
if (isWindows) return '%APPDATA%\\Claude\\claude_desktop_config.json';
|
||||
return '~/.config/Claude/claude_desktop_config.json';
|
||||
case 'cursor':
|
||||
if (isWindows) return '%USERPROFILE%\\.cursor\\mcp.json';
|
||||
return '~/.cursor/mcp.json';
|
||||
case 'codex':
|
||||
return '~/.codex/config.json';
|
||||
case 'zed':
|
||||
if (isMac) return '~/Library/Application Support/Zed/settings.json';
|
||||
if (isWindows) return '%APPDATA%\\Zed\\settings.json';
|
||||
return '~/.config/zed/settings.json';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JSON snippet builders
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildSnippet(client: McpClient, binaryPath: string): string {
|
||||
if (client === 'zed') {
|
||||
return JSON.stringify(
|
||||
{ context_servers: { openhuman: { command: { path: binaryPath, args: ['mcp'] } } } },
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
// Claude Desktop, Cursor, Codex
|
||||
return JSON.stringify(
|
||||
{ mcpServers: { openhuman: { command: binaryPath, args: ['mcp'] } } },
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// McpServerPanel component
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const McpServerPanel = () => {
|
||||
const { t } = useT();
|
||||
const { navigateBack, breadcrumbs } = useSettingsNavigation();
|
||||
|
||||
const [binaryInfo, setBinaryInfo] = useState<McpBinaryInfo | null>(null);
|
||||
const [binaryError, setBinaryError] = useState<string | null>(null);
|
||||
const [activeClient, setActiveClient] = useState<McpClient>('claude-desktop');
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [openConfigError, setOpenConfigError] = useState<string | null>(null);
|
||||
|
||||
// Resolve the binary path on mount.
|
||||
useEffect(() => {
|
||||
log('resolving mcp binary path');
|
||||
invoke<McpBinaryInfo>('mcp_resolve_binary_path')
|
||||
.then(info => {
|
||||
log('mcp binary resolved: %s os: %s', info.path, info.os);
|
||||
setBinaryInfo(info);
|
||||
setBinaryError(null);
|
||||
})
|
||||
.catch(err => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
log('mcp binary resolution failed: %s', msg);
|
||||
setBinaryError(msg);
|
||||
setBinaryInfo(null);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const binaryPath = binaryInfo?.path ?? null;
|
||||
// When binary resolution fails, fall back to navigator.userAgent so Windows/Linux
|
||||
// users see the correct config file path instead of the macOS default.
|
||||
const os =
|
||||
binaryInfo?.os ??
|
||||
(/win/i.test(navigator.userAgent) && !/mac/i.test(navigator.userAgent)
|
||||
? 'windows'
|
||||
: /linux/i.test(navigator.userAgent)
|
||||
? 'linux'
|
||||
: 'macos');
|
||||
const displayPath = binaryPath ?? t('settings.mcpServer.binaryPathNotFound');
|
||||
const snippet = buildSnippet(activeClient, displayPath);
|
||||
const configPath = configFilePathFor(activeClient, os);
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(snippet);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch {
|
||||
// Clipboard write failed — silently ignore.
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenConfig = async () => {
|
||||
setOpenConfigError(null);
|
||||
try {
|
||||
await invoke('mcp_open_client_config', { client: activeClient });
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setOpenConfigError(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const clients: { id: McpClient; label: string }[] = [
|
||||
{ id: 'claude-desktop', label: t('settings.mcpServer.clientClaudeDesktop') },
|
||||
{ id: 'cursor', label: t('settings.mcpServer.clientCursor') },
|
||||
{ id: 'codex', label: t('settings.mcpServer.clientCodex') },
|
||||
{ id: 'zed', label: t('settings.mcpServer.clientZed') },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="z-10 relative">
|
||||
<SettingsHeader
|
||||
title={t('settings.mcpServer.title')}
|
||||
showBackButton={true}
|
||||
onBack={navigateBack}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
|
||||
{/* ----------------------------------------------------------------- */}
|
||||
{/* Section 1 — Available Tools */}
|
||||
{/* ----------------------------------------------------------------- */}
|
||||
<div className="px-4 pt-4 pb-2">
|
||||
<div className="text-sm font-semibold text-slate-900 dark:text-neutral-100 mb-0.5">
|
||||
{t('settings.mcpServer.toolsSectionTitle')}
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 dark:text-neutral-400 mb-3">
|
||||
{t('settings.mcpServer.toolsSectionDesc')}
|
||||
</div>
|
||||
<div className="rounded-xl border border-stone-200 dark:border-neutral-800 divide-y divide-stone-100 dark:divide-neutral-800 overflow-hidden">
|
||||
{MCP_TOOLS.map(tool => (
|
||||
<div
|
||||
key={tool.name}
|
||||
className="flex items-start gap-3 px-4 py-2.5 bg-white dark:bg-neutral-900">
|
||||
<span className="font-mono text-xs text-primary-700 dark:text-primary-400 mt-0.5 shrink-0">
|
||||
{tool.name}
|
||||
</span>
|
||||
<span className="text-xs text-slate-600 dark:text-neutral-400">
|
||||
{tool.description}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ----------------------------------------------------------------- */}
|
||||
{/* Section 2 — Client Configuration */}
|
||||
{/* ----------------------------------------------------------------- */}
|
||||
<div className="px-4 pt-4 pb-6">
|
||||
<div className="text-sm font-semibold text-slate-900 dark:text-neutral-100 mb-0.5">
|
||||
{t('settings.mcpServer.configSectionTitle')}
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 dark:text-neutral-400 mb-3">
|
||||
{t('settings.mcpServer.configSectionDesc')}
|
||||
</div>
|
||||
|
||||
{/* Client selector tabs */}
|
||||
<div
|
||||
className="flex gap-1 mb-4 flex-wrap"
|
||||
role="tablist"
|
||||
aria-label={t('settings.mcpServer.clientSelectorAriaLabel')}>
|
||||
{clients.map(client => (
|
||||
<button
|
||||
key={client.id}
|
||||
role="tab"
|
||||
aria-selected={activeClient === client.id}
|
||||
onClick={() => {
|
||||
setActiveClient(client.id);
|
||||
setOpenConfigError(null);
|
||||
}}
|
||||
className={[
|
||||
'px-3 py-1.5 rounded-lg text-xs font-medium transition-colors',
|
||||
activeClient === client.id
|
||||
? 'bg-primary-600 text-white'
|
||||
: 'bg-stone-100 dark:bg-neutral-800 text-slate-700 dark:text-neutral-300 hover:bg-stone-200 dark:hover:bg-neutral-700',
|
||||
].join(' ')}>
|
||||
{client.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Binary path error banner */}
|
||||
{binaryError && (
|
||||
<div className="mb-3 px-3 py-2 rounded-lg border border-coral-300 dark:border-coral-500/40 bg-coral-50 dark:bg-coral-500/10 text-xs text-coral-900 dark:text-coral-300">
|
||||
{t('settings.mcpServer.binaryPathNotFound')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Config file path */}
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<span className="text-xs text-slate-500 dark:text-neutral-400 shrink-0">
|
||||
{t('settings.mcpServer.configFilePath')}:
|
||||
</span>
|
||||
<span className="text-xs font-mono text-slate-700 dark:text-neutral-300 truncate">
|
||||
{configPath}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* JSON snippet */}
|
||||
<div className="rounded-xl overflow-hidden border border-stone-200 dark:border-neutral-800 mb-3">
|
||||
<pre className="bg-stone-50 dark:bg-neutral-900/60 px-4 py-3 text-xs font-mono text-slate-800 dark:text-neutral-200 overflow-x-auto whitespace-pre leading-relaxed">
|
||||
{snippet}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium bg-slate-700 hover:bg-slate-600 text-white transition-colors shrink-0">
|
||||
{copied ? t('settings.mcpServer.copied') : t('settings.mcpServer.copySnippet')}
|
||||
</button>
|
||||
|
||||
{isTauri() && (
|
||||
<button
|
||||
onClick={handleOpenConfig}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium bg-stone-100 dark:bg-neutral-800 text-slate-700 dark:text-neutral-300 hover:bg-stone-200 dark:hover:bg-neutral-700 transition-colors shrink-0">
|
||||
{t('settings.mcpServer.openConfigFile')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Open config error */}
|
||||
{openConfigError && (
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="mt-2 text-xs text-coral-600 dark:text-coral-300">
|
||||
{t('settings.mcpServer.openConfigError')}: {openConfigError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default McpServerPanel;
|
||||
@@ -474,6 +474,27 @@ const ar5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default ar5;
|
||||
|
||||
@@ -480,6 +480,27 @@ const bn5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default bn5;
|
||||
|
||||
@@ -480,6 +480,27 @@ const en5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default en5;
|
||||
|
||||
@@ -485,6 +485,27 @@ const es5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default es5;
|
||||
|
||||
@@ -489,6 +489,27 @@ const fr5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default fr5;
|
||||
|
||||
@@ -482,6 +482,27 @@ const hi5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default hi5;
|
||||
|
||||
@@ -482,6 +482,27 @@ const id5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default id5;
|
||||
|
||||
@@ -486,6 +486,27 @@ const it5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default it5;
|
||||
|
||||
@@ -443,6 +443,27 @@ const ko5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': '노랑',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman 라이브러리를 사용할 수 없음',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default ko5;
|
||||
|
||||
@@ -486,6 +486,27 @@ const pt5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default pt5;
|
||||
|
||||
@@ -483,6 +483,27 @@ const ru5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default ru5;
|
||||
|
||||
@@ -455,6 +455,27 @@ const zhCN5: TranslationMap = {
|
||||
'settings.mascot.colorYellow': 'Yellow',
|
||||
'settings.mascot.libraryUnavailable': 'OpenHuman library unavailable',
|
||||
'settings.mascot.title': 'OpenHuman',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
};
|
||||
|
||||
export default zhCN5;
|
||||
|
||||
@@ -1956,6 +1956,27 @@ const en: TranslationMap = {
|
||||
'settings.developerMenu.integrationTriggers.title': 'Integration Triggers',
|
||||
'settings.developerMenu.integrationTriggers.desc':
|
||||
'Configure AI triage settings for Composio integration triggers',
|
||||
'settings.developerMenu.mcpServer.title': 'MCP Server',
|
||||
'settings.developerMenu.mcpServer.desc': 'Configure external MCP clients to connect to OpenHuman',
|
||||
'settings.mcpServer.title': 'MCP Server',
|
||||
'settings.mcpServer.toolsSectionTitle': 'Available Tools',
|
||||
'settings.mcpServer.toolsSectionDesc':
|
||||
'Tools exposed via the MCP stdio server when running openhuman-core mcp',
|
||||
'settings.mcpServer.configSectionTitle': 'Client Configuration',
|
||||
'settings.mcpServer.configSectionDesc':
|
||||
'Select your MCP client to generate the correct configuration snippet',
|
||||
'settings.mcpServer.copySnippet': 'Copy to Clipboard',
|
||||
'settings.mcpServer.copied': 'Copied!',
|
||||
'settings.mcpServer.openConfigFile': 'Open Config File',
|
||||
'settings.mcpServer.binaryPathNotFound':
|
||||
'OpenHuman binary not found. If running from source, build with: cargo build --bin openhuman-core',
|
||||
'settings.mcpServer.openConfigError': 'Failed to open config file',
|
||||
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
|
||||
'settings.mcpServer.clientCursor': 'Cursor',
|
||||
'settings.mcpServer.clientCodex': 'Codex',
|
||||
'settings.mcpServer.clientZed': 'Zed',
|
||||
'settings.mcpServer.configFilePath': 'Config file',
|
||||
'settings.mcpServer.clientSelectorAriaLabel': 'MCP client selector',
|
||||
'settings.appearance.menuDesc': 'Pick light, dark, or match your system theme',
|
||||
'settings.appearance.title': 'Appearance',
|
||||
'settings.appearance.themeHeading': 'Theme',
|
||||
|
||||
@@ -16,6 +16,7 @@ import CronJobsPanel from '../components/settings/panels/CronJobsPanel';
|
||||
import DeveloperOptionsPanel from '../components/settings/panels/DeveloperOptionsPanel';
|
||||
import LocalModelDebugPanel from '../components/settings/panels/LocalModelDebugPanel';
|
||||
import MascotPanel from '../components/settings/panels/MascotPanel';
|
||||
import McpServerPanel from '../components/settings/panels/McpServerPanel';
|
||||
import MemoryDataPanel from '../components/settings/panels/MemoryDataPanel';
|
||||
import MemoryDebugPanel from '../components/settings/panels/MemoryDebugPanel';
|
||||
import MessagingPanel from '../components/settings/panels/MessagingPanel';
|
||||
@@ -352,6 +353,7 @@ const Settings = () => {
|
||||
<Route path="companion" element={wrapSettingsPage(<CompanionPanel />)} />
|
||||
{/* Developer Options */}
|
||||
<Route path="developer-options" element={wrapSettingsPage(<DeveloperOptionsPanel />)} />
|
||||
<Route path="mcp-server" element={wrapSettingsPage(<McpServerPanel />)} />
|
||||
<Route
|
||||
path="notification-routing"
|
||||
element={wrapSettingsPage(<NotificationRoutingPanel />)}
|
||||
|
||||
Reference in New Issue
Block a user