diff --git a/src-tauri/src/core/cmd.rs b/src-tauri/src/core/cmd.rs index ca3d051af..3d7d921ee 100644 --- a/src-tauri/src/core/cmd.rs +++ b/src-tauri/src/core/cmd.rs @@ -7,6 +7,9 @@ use tauri::{AppHandle, Manager, Runtime, State}; use super::{server, setup, state::AppState}; const CONFIGURATION_FILE_NAME: &str = "settings.json"; +const DEFAULT_MCP_CONFIG: &str = r#"{ + "mcpServers": {} +}"#; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AppConfiguration { @@ -352,3 +355,29 @@ pub async fn call_tool( Err(format!("Tool {} not found", tool_name)) } + +#[tauri::command] +pub async fn get_mcp_configs(app: AppHandle) -> Result { + let mut path = get_jan_data_folder_path(app); + path.push("mcp_config.json"); + log::info!("read mcp configs, path: {:?}", path); + + // Create default empty config if file doesn't exist + if !path.exists() { + log::info!("mcp_config.json not found, creating default empty config"); + fs::write(&path, DEFAULT_MCP_CONFIG) + .map_err(|e| format!("Failed to create default MCP config: {}", e))?; + } + + let contents = fs::read_to_string(path).map_err(|e| e.to_string())?; + return Ok(contents); +} + +#[tauri::command] +pub async fn save_mcp_configs(app: AppHandle, configs: String) -> Result<(), String> { + let mut path = get_jan_data_folder_path(app); + path.push("mcp_config.json"); + log::info!("save mcp configs, path: {:?}", path); + + fs::write(path, configs).map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f37d97ae2..7248e15cc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -39,6 +39,8 @@ pub fn run() { core::cmd::app_token, core::cmd::start_server, core::cmd::stop_server, + core::cmd::save_mcp_configs, + core::cmd::get_mcp_configs, // MCP commands core::cmd::get_tools, core::cmd::call_tool, diff --git a/web/package.json b/web/package.json index 7999c74e9..cdf2d8d8b 100644 --- a/web/package.json +++ b/web/package.json @@ -37,6 +37,7 @@ "marked": "^9.1.2", "next": "14.2.3", "next-themes": "^0.2.1", + "npx-scope-finder": "^1.3.0", "openai": "^4.90.0", "postcss": "8.4.31", "postcss-url": "10.1.3", diff --git a/web/screens/Settings/MCP/configuration.tsx b/web/screens/Settings/MCP/configuration.tsx new file mode 100644 index 000000000..b829066d2 --- /dev/null +++ b/web/screens/Settings/MCP/configuration.tsx @@ -0,0 +1,98 @@ +import React, { useState, useEffect, useCallback } from 'react' + +import { Button, TextArea } from '@janhq/joi' +import { useAtomValue } from 'jotai' + +import { janDataFolderPathAtom } from '@/helpers/atoms/AppConfig.atom' + +const MCPConfiguration = () => { + const janDataFolderPath = useAtomValue(janDataFolderPathAtom) + const [configContent, setConfigContent] = useState('') + const [isSaving, setIsSaving] = useState(false) + const [error, setError] = useState('') + const [success, setSuccess] = useState('') + + const readConfigFile = useCallback(async () => { + try { + // Read the file + const content = await window.core?.api.getMcpConfigs() + setConfigContent(content) + + setError('') + } catch (err) { + console.error('Error reading config file:', err) + setError('Failed to read config file') + } + }, [janDataFolderPath]) + + useEffect(() => { + if (janDataFolderPath) { + readConfigFile() + } + }, [janDataFolderPath, readConfigFile]) + + const saveConfigFile = useCallback(async () => { + try { + setIsSaving(true) + setSuccess('') + setError('') + + // Validate JSON + try { + JSON.parse(configContent) + } catch (err) { + setError('Invalid JSON format') + setIsSaving(false) + return + } + await window.core?.api?.saveMcpConfigs({ configs: configContent }) + + setSuccess('Config saved successfully') + setIsSaving(false) + } catch (err) { + console.error('Error saving config file:', err) + setError('Failed to save config file') + setIsSaving(false) + } + }, [janDataFolderPath, configContent]) + + return ( + <> + {error && ( +
+ {error} +
+ )} + + {success && ( +
+ {success} +
+ )} + +
+ +