mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-28 23:06:41 +02:00
feat: add mcp
This commit is contained in:
@@ -322,10 +322,12 @@ impl ApiServer {
|
||||
|
||||
let api = ApiDoc::openapi();
|
||||
|
||||
let v1_routes = v1_routes.layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
auth_middleware,
|
||||
));
|
||||
let v1_routes = v1_routes
|
||||
.layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
auth_middleware,
|
||||
))
|
||||
.layer(middleware::from_fn(terms_check_middleware));
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/v1", v1_routes)
|
||||
@@ -363,6 +365,19 @@ impl ApiServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Terms and Conditions check middleware
|
||||
async fn terms_check_middleware(
|
||||
request: axum::extract::Request,
|
||||
next: Next,
|
||||
) -> Result<Response, StatusCode> {
|
||||
// Check if Wayfern terms have been accepted
|
||||
if !crate::wayfern_terms::WayfernTermsManager::instance().is_terms_accepted() {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
Ok(next.run(request).await)
|
||||
}
|
||||
|
||||
// Authentication middleware
|
||||
async fn auth_middleware(
|
||||
State(state): State<ApiServerState>,
|
||||
|
||||
@@ -635,6 +635,11 @@ impl Downloader {
|
||||
browser_str: String,
|
||||
version: String,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Check if Wayfern terms have been accepted before allowing any browser downloads
|
||||
if !crate::wayfern_terms::WayfernTermsManager::instance().is_terms_accepted() {
|
||||
return Err("Please accept Wayfern Terms and Conditions before downloading browsers".into());
|
||||
}
|
||||
|
||||
// Check if this browser-version pair is already being downloaded
|
||||
let download_key = format!("{browser_str}-{version}");
|
||||
{
|
||||
|
||||
+60
-1
@@ -34,8 +34,11 @@ mod settings_manager;
|
||||
pub mod sync;
|
||||
pub mod traffic_stats;
|
||||
mod wayfern_manager;
|
||||
mod wayfern_terms;
|
||||
// mod theme_detector; // removed: theme detection handled in webview via CSS prefers-color-scheme
|
||||
mod commercial_license;
|
||||
mod cookie_manager;
|
||||
mod mcp_server;
|
||||
mod tag_manager;
|
||||
mod version_updater;
|
||||
|
||||
@@ -235,6 +238,54 @@ async fn copy_profile_cookies(
|
||||
cookie_manager::CookieManager::copy_cookies(&app_handle, request).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn check_wayfern_terms_accepted() -> bool {
|
||||
wayfern_terms::WayfernTermsManager::instance().is_terms_accepted()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn accept_wayfern_terms() -> Result<(), String> {
|
||||
wayfern_terms::WayfernTermsManager::instance()
|
||||
.accept_terms()
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_commercial_trial_status(
|
||||
app_handle: tauri::AppHandle,
|
||||
) -> Result<commercial_license::TrialStatus, String> {
|
||||
commercial_license::CommercialLicenseManager::instance()
|
||||
.get_trial_status(&app_handle)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn acknowledge_trial_expiration(app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
commercial_license::CommercialLicenseManager::instance()
|
||||
.acknowledge_expiration(&app_handle)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn has_acknowledged_trial_expiration(app_handle: tauri::AppHandle) -> Result<bool, String> {
|
||||
commercial_license::CommercialLicenseManager::instance().has_acknowledged(&app_handle)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn start_mcp_server(app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||
mcp_server::McpServer::instance().start(app_handle).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn stop_mcp_server() -> Result<(), String> {
|
||||
mcp_server::McpServer::instance().stop().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_mcp_server_status() -> bool {
|
||||
mcp_server::McpServer::instance().is_running()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn is_geoip_database_available() -> Result<bool, String> {
|
||||
Ok(GeoIPDownloader::is_geoip_database_available())
|
||||
@@ -841,7 +892,15 @@ pub fn run() {
|
||||
is_proxy_in_use_by_synced_profile,
|
||||
is_group_in_use_by_synced_profile,
|
||||
read_profile_cookies,
|
||||
copy_profile_cookies
|
||||
copy_profile_cookies,
|
||||
check_wayfern_terms_accepted,
|
||||
accept_wayfern_terms,
|
||||
get_commercial_trial_status,
|
||||
acknowledge_trial_expiration,
|
||||
has_acknowledged_trial_expiration,
|
||||
start_mcp_server,
|
||||
stop_mcp_server,
|
||||
get_mcp_server_status
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tauri::AppHandle;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
|
||||
use crate::profile::{BrowserProfile, ProfileManager};
|
||||
use crate::proxy_manager::PROXY_MANAGER;
|
||||
use crate::wayfern_terms::WayfernTermsManager;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct McpTool {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub input_schema: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct McpRequest {
|
||||
jsonrpc: String,
|
||||
id: serde_json::Value,
|
||||
method: String,
|
||||
params: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct McpResponse {
|
||||
jsonrpc: String,
|
||||
id: serde_json::Value,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
result: Option<serde_json::Value>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<McpError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct McpError {
|
||||
code: i32,
|
||||
message: String,
|
||||
}
|
||||
|
||||
struct McpServerInner {
|
||||
app_handle: Option<AppHandle>,
|
||||
}
|
||||
|
||||
pub struct McpServer {
|
||||
inner: Arc<AsyncMutex<McpServerInner>>,
|
||||
is_running: AtomicBool,
|
||||
}
|
||||
|
||||
impl McpServer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(AsyncMutex::new(McpServerInner { app_handle: None })),
|
||||
is_running: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance() -> &'static McpServer {
|
||||
&MCP_SERVER
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.is_running.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub async fn start(&self, app_handle: AppHandle) -> Result<(), String> {
|
||||
// Check terms acceptance first
|
||||
if !WayfernTermsManager::instance().is_terms_accepted() {
|
||||
return Err(
|
||||
"Wayfern Terms and Conditions must be accepted before starting MCP server".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if self.is_running() {
|
||||
return Err("MCP server is already running".to_string());
|
||||
}
|
||||
|
||||
let mut inner = self.inner.lock().await;
|
||||
inner.app_handle = Some(app_handle);
|
||||
self.is_running.store(true, Ordering::SeqCst);
|
||||
|
||||
log::info!("MCP server started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop(&self) -> Result<(), String> {
|
||||
if !self.is_running() {
|
||||
return Err("MCP server is not running".to_string());
|
||||
}
|
||||
|
||||
let mut inner = self.inner.lock().await;
|
||||
inner.app_handle = None;
|
||||
self.is_running.store(false, Ordering::SeqCst);
|
||||
|
||||
log::info!("MCP server stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_tools(&self) -> Vec<McpTool> {
|
||||
vec![
|
||||
McpTool {
|
||||
name: "list_profiles".to_string(),
|
||||
description: "List all Wayfern and Camoufox browser profiles".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}),
|
||||
},
|
||||
McpTool {
|
||||
name: "get_profile".to_string(),
|
||||
description: "Get details of a specific browser profile".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile_id": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the profile to retrieve"
|
||||
}
|
||||
},
|
||||
"required": ["profile_id"]
|
||||
}),
|
||||
},
|
||||
McpTool {
|
||||
name: "run_profile".to_string(),
|
||||
description: "Launch a browser profile with an optional URL".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile_id": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the profile to launch"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Optional URL to open in the browser"
|
||||
},
|
||||
"headless": {
|
||||
"type": "boolean",
|
||||
"description": "Run the browser in headless mode"
|
||||
}
|
||||
},
|
||||
"required": ["profile_id"]
|
||||
}),
|
||||
},
|
||||
McpTool {
|
||||
name: "kill_profile".to_string(),
|
||||
description: "Stop a running browser profile".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile_id": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the profile to stop"
|
||||
}
|
||||
},
|
||||
"required": ["profile_id"]
|
||||
}),
|
||||
},
|
||||
McpTool {
|
||||
name: "list_proxies".to_string(),
|
||||
description: "List all configured proxies".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}),
|
||||
},
|
||||
McpTool {
|
||||
name: "get_profile_status".to_string(),
|
||||
description: "Check if a browser profile is currently running".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile_id": {
|
||||
"type": "string",
|
||||
"description": "The UUID of the profile to check"
|
||||
}
|
||||
},
|
||||
"required": ["profile_id"]
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub async fn handle_request(&self, request: McpRequest) -> McpResponse {
|
||||
if !self.is_running() {
|
||||
return McpResponse {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
id: request.id,
|
||||
result: None,
|
||||
error: Some(McpError {
|
||||
code: -32001,
|
||||
message: "MCP server is not running".to_string(),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
let result = match request.method.as_str() {
|
||||
"tools/list" => self.handle_tools_list().await,
|
||||
"tools/call" => self.handle_tool_call(request.params).await,
|
||||
_ => Err(McpError {
|
||||
code: -32601,
|
||||
message: format!("Method not found: {}", request.method),
|
||||
}),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(value) => McpResponse {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
id: request.id,
|
||||
result: Some(value),
|
||||
error: None,
|
||||
},
|
||||
Err(error) => McpResponse {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
id: request.id,
|
||||
result: None,
|
||||
error: Some(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_tools_list(&self) -> Result<serde_json::Value, McpError> {
|
||||
Ok(serde_json::json!({
|
||||
"tools": self.get_tools()
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_tool_call(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value, McpError> {
|
||||
let params = params.ok_or_else(|| McpError {
|
||||
code: -32602,
|
||||
message: "Missing parameters".to_string(),
|
||||
})?;
|
||||
|
||||
let tool_name = params
|
||||
.get("name")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32602,
|
||||
message: "Missing tool name".to_string(),
|
||||
})?;
|
||||
|
||||
let arguments = params
|
||||
.get("arguments")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::json!({}));
|
||||
|
||||
match tool_name {
|
||||
"list_profiles" => self.handle_list_profiles().await,
|
||||
"get_profile" => self.handle_get_profile(&arguments).await,
|
||||
"run_profile" => self.handle_run_profile(&arguments).await,
|
||||
"kill_profile" => self.handle_kill_profile(&arguments).await,
|
||||
"list_proxies" => self.handle_list_proxies().await,
|
||||
"get_profile_status" => self.handle_get_profile_status(&arguments).await,
|
||||
_ => Err(McpError {
|
||||
code: -32602,
|
||||
message: format!("Unknown tool: {tool_name}"),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_list_profiles(&self) -> Result<serde_json::Value, McpError> {
|
||||
let profiles = ProfileManager::instance()
|
||||
.list_profiles()
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: format!("Failed to list profiles: {e}"),
|
||||
})?;
|
||||
|
||||
// Filter to only Wayfern and Camoufox profiles
|
||||
let filtered: Vec<&BrowserProfile> = profiles
|
||||
.iter()
|
||||
.filter(|p| p.browser == "wayfern" || p.browser == "camoufox")
|
||||
.collect();
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": serde_json::to_string_pretty(&filtered).unwrap_or_default()
|
||||
}]
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_get_profile(
|
||||
&self,
|
||||
arguments: &serde_json::Value,
|
||||
) -> Result<serde_json::Value, McpError> {
|
||||
let profile_id = arguments
|
||||
.get("profile_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32602,
|
||||
message: "Missing profile_id".to_string(),
|
||||
})?;
|
||||
|
||||
let profiles = ProfileManager::instance()
|
||||
.list_profiles()
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: format!("Failed to list profiles: {e}"),
|
||||
})?;
|
||||
|
||||
let profile = profiles
|
||||
.iter()
|
||||
.find(|p| p.id.to_string() == profile_id)
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32000,
|
||||
message: format!("Profile not found: {profile_id}"),
|
||||
})?;
|
||||
|
||||
// Check if it's a Wayfern or Camoufox profile
|
||||
if profile.browser != "wayfern" && profile.browser != "camoufox" {
|
||||
return Err(McpError {
|
||||
code: -32000,
|
||||
message: "MCP only supports Wayfern and Camoufox profiles".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": serde_json::to_string_pretty(&profile).unwrap_or_default()
|
||||
}]
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_run_profile(
|
||||
&self,
|
||||
arguments: &serde_json::Value,
|
||||
) -> Result<serde_json::Value, McpError> {
|
||||
let profile_id = arguments
|
||||
.get("profile_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32602,
|
||||
message: "Missing profile_id".to_string(),
|
||||
})?;
|
||||
|
||||
let url = arguments.get("url").and_then(|v| v.as_str());
|
||||
let _headless = arguments
|
||||
.get("headless")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
// Get the profile
|
||||
let profiles = ProfileManager::instance()
|
||||
.list_profiles()
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: format!("Failed to list profiles: {e}"),
|
||||
})?;
|
||||
|
||||
let profile = profiles
|
||||
.iter()
|
||||
.find(|p| p.id.to_string() == profile_id)
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32000,
|
||||
message: format!("Profile not found: {profile_id}"),
|
||||
})?;
|
||||
|
||||
// Check if it's a Wayfern or Camoufox profile
|
||||
if profile.browser != "wayfern" && profile.browser != "camoufox" {
|
||||
return Err(McpError {
|
||||
code: -32000,
|
||||
message: "MCP only supports Wayfern and Camoufox profiles".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Get app handle to launch
|
||||
let inner = self.inner.lock().await;
|
||||
let app_handle = inner.app_handle.as_ref().ok_or_else(|| McpError {
|
||||
code: -32000,
|
||||
message: "MCP server not properly initialized".to_string(),
|
||||
})?;
|
||||
|
||||
// Launch the browser
|
||||
crate::browser_runner::BrowserRunner::instance()
|
||||
.launch_browser(
|
||||
app_handle.clone(),
|
||||
profile,
|
||||
url.map(|s| s.to_string()),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: format!("Failed to launch browser: {e}"),
|
||||
})?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": format!("Browser profile '{}' launched successfully", profile.name)
|
||||
}]
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_kill_profile(
|
||||
&self,
|
||||
arguments: &serde_json::Value,
|
||||
) -> Result<serde_json::Value, McpError> {
|
||||
let profile_id = arguments
|
||||
.get("profile_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32602,
|
||||
message: "Missing profile_id".to_string(),
|
||||
})?;
|
||||
|
||||
// Get the profile
|
||||
let profiles = ProfileManager::instance()
|
||||
.list_profiles()
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: format!("Failed to list profiles: {e}"),
|
||||
})?;
|
||||
|
||||
let profile = profiles
|
||||
.iter()
|
||||
.find(|p| p.id.to_string() == profile_id)
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32000,
|
||||
message: format!("Profile not found: {profile_id}"),
|
||||
})?;
|
||||
|
||||
// Check if it's a Wayfern or Camoufox profile
|
||||
if profile.browser != "wayfern" && profile.browser != "camoufox" {
|
||||
return Err(McpError {
|
||||
code: -32000,
|
||||
message: "MCP only supports Wayfern and Camoufox profiles".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Get app handle to kill
|
||||
let inner = self.inner.lock().await;
|
||||
let app_handle = inner.app_handle.as_ref().ok_or_else(|| McpError {
|
||||
code: -32000,
|
||||
message: "MCP server not properly initialized".to_string(),
|
||||
})?;
|
||||
|
||||
// Kill the browser
|
||||
crate::browser_runner::BrowserRunner::instance()
|
||||
.kill_browser_process(app_handle.clone(), profile)
|
||||
.await
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: format!("Failed to kill browser: {e}"),
|
||||
})?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": format!("Browser profile '{}' stopped successfully", profile.name)
|
||||
}]
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_list_proxies(&self) -> Result<serde_json::Value, McpError> {
|
||||
let proxies = PROXY_MANAGER.get_stored_proxies();
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": serde_json::to_string_pretty(&proxies).unwrap_or_default()
|
||||
}]
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_get_profile_status(
|
||||
&self,
|
||||
arguments: &serde_json::Value,
|
||||
) -> Result<serde_json::Value, McpError> {
|
||||
let profile_id = arguments
|
||||
.get("profile_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32602,
|
||||
message: "Missing profile_id".to_string(),
|
||||
})?;
|
||||
|
||||
// Get the profile
|
||||
let profiles = ProfileManager::instance()
|
||||
.list_profiles()
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: format!("Failed to list profiles: {e}"),
|
||||
})?;
|
||||
|
||||
let profile = profiles
|
||||
.iter()
|
||||
.find(|p| p.id.to_string() == profile_id)
|
||||
.ok_or_else(|| McpError {
|
||||
code: -32000,
|
||||
message: format!("Profile not found: {profile_id}"),
|
||||
})?;
|
||||
|
||||
// Check if it's a Wayfern or Camoufox profile
|
||||
if profile.browser != "wayfern" && profile.browser != "camoufox" {
|
||||
return Err(McpError {
|
||||
code: -32000,
|
||||
message: "MCP only supports Wayfern and Camoufox profiles".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let is_running = profile.process_id.is_some();
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": serde_json::json!({
|
||||
"profile_id": profile_id,
|
||||
"is_running": is_running
|
||||
}).to_string()
|
||||
}]
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref MCP_SERVER: McpServer = McpServer::new();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mcp_tools_count() {
|
||||
let server = McpServer::new();
|
||||
let tools = server.get_tools();
|
||||
|
||||
// Should have at least these tools
|
||||
assert!(tools.len() >= 5);
|
||||
|
||||
// Check tool names
|
||||
let tool_names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
|
||||
assert!(tool_names.contains(&"list_profiles"));
|
||||
assert!(tool_names.contains(&"get_profile"));
|
||||
assert!(tool_names.contains(&"run_profile"));
|
||||
assert!(tool_names.contains(&"kill_profile"));
|
||||
assert!(tool_names.contains(&"list_proxies"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mcp_server_initial_state() {
|
||||
let server = McpServer::new();
|
||||
assert!(!server.is_running());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user