feat(core): Added node configuration schema support

- Imported the ConfigurationSchema class to define core type configurations
- Added CoreTypes constant support for xray and v2fly configuration modes
- Replaced parameter inputs in addNode.vue with node type selectors
- Created the nodeConfigurator.vue component to handle configuration structure
- Added Zod mode validation support for xray and v2fly configurations
- Updated the README document to explain the project build and development startup steps
- Added MCP HTTP service support tool list and state control interface
- Imported the Rocket.rs dependency and configured the background service runtime environment
This commit is contained in:
2025-11-30 23:36:48 +08:00
parent 8eaac4df96
commit b085ba3c60
11 changed files with 784 additions and 9 deletions

View File

@@ -27,3 +27,6 @@ tauri-plugin-fs = "2"
tauri-plugin-os = "2"
tauri-plugin-shell = "2"
tauri-plugin-process = "2"
rocket = { version = "0.5", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
uuid = { version = "1.0", features = ["v4", "serde"] }

View File

@@ -1,9 +1,12 @@
use tauri::Manager;
use crate::spary::spary_switch;
use std::sync::Arc;
use tauri::menu::{Menu, MenuItem};
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
use tauri::Manager;
use tauri_plugin_sql::{Migration, MigrationKind};
use tokio::sync::Mutex;
mod mcp_server;
mod spary;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
@@ -71,8 +74,60 @@ pub fn run() {
})
.build(app)?;
tray.set_menu(Some(menu))?;
// Start the MCP HTTP server in a background task
let app_handle = app.handle().clone();
std::thread::spawn(move || {
start_mcp_server(app_handle);
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
fn start_mcp_server(app_handle: tauri::AppHandle) {
// Disable MCP server for now
if false {
return;
}
use mcp_server::AppState;
// Create the shared application state
let state = AppState {
app_handle,
spray_status: Arc::new(Mutex::new(false)), // Initial spray status is off
};
// Configure Rocket with custom settings
let rocket = rocket::build()
.configure(rocket::Config {
port: 3000,
address: "127.0.0.1".parse().expect("Invalid address"),
..rocket::Config::default()
})
.manage(state)
.mount(
"/",
rocket::routes![
mcp_server::initialize,
mcp_server::get_capabilities,
mcp_server::list_tools,
mcp_server::call_tool,
mcp_server::set_spray_status,
mcp_server::get_spray_status
],
);
println!("MCP server running on http://127.0.0.1:3000");
// Start the server
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
rocket.launch().await.unwrap();
});
}

181
src-tauri/src/mcp_server.rs Normal file
View File

@@ -0,0 +1,181 @@
use rocket::{State, serde::{Deserialize, Serialize, json::Json}};
use serde_json::{json, Value};
use std::sync::Arc;
use tokio::sync::Mutex;
use tauri::AppHandle;
use crate::spary::spary_switch;
#[derive(Clone)]
pub struct AppState {
pub app_handle: AppHandle,
pub spray_status: Arc<Mutex<bool>>,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct InitializeResponse {
protocol_version: String,
server_info: ServerInfo,
capabilities: Capabilities,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct ServerInfo {
name: String,
version: String,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Capabilities {
experimental: Option<Value>,
tools: Option<Value>,
}
#[rocket::post("/mcp/initialize", data = "<_data>")]
pub async fn initialize(_state: &State<AppState>, _data: Json<Value>) -> Json<InitializeResponse> {
Json(InitializeResponse {
protocol_version: "1.0".to_string(),
server_info: ServerInfo {
name: "spary-mcp-server".to_string(),
version: "0.1.0".to_string(),
},
capabilities: Capabilities {
experimental: None,
tools: Some(json!({
"list": true,
"call": true
})),
},
})
}
#[rocket::get("/mcp/capabilities")]
pub async fn get_capabilities(_state: &State<AppState>) -> Json<Value> {
Json(json!({
"tools": {
"list": true,
"call": true
}
}))
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct Tool {
name: String,
description: String,
input_schema: Value,
}
#[rocket::get("/mcp/tools/list")]
pub async fn list_tools(_state: &State<AppState>) -> Json<Value> {
let tools = vec![
Tool {
name: "spray_toggle".to_string(),
description: "Toggle the spray functionality on or off".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"status": {
"type": "boolean",
"description": "Whether to turn spray on (true) or off (false)"
}
},
"required": ["status"]
}),
}
];
Json(json!({ "tools": tools }))
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct ToolCallRequest {
name: String,
arguments: Value,
}
#[rocket::post("/mcp/tools/call", data = "<payload>")]
pub async fn call_tool(
state: &State<AppState>,
payload: Json<ToolCallRequest>,
) -> Result<Json<Value>, rocket::http::Status> {
match payload.name.as_str() {
"spray_toggle" => {
if let Some(status) = payload.arguments.get("status").and_then(|v| v.as_bool()) {
// Call the existing spray_switch function
spary_switch(status);
// Update the spray status in the shared state
{
let mut spray_status = state.spray_status.lock().await;
*spray_status = status;
}
Ok(Json(json!({
"result": {
"success": true,
"message": format!("Spray toggled to {}", status)
}
})))
} else {
Err(rocket::http::Status::BadRequest)
}
}
_ => Err(rocket::http::Status::NotFound),
}
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct SprayRequest {
status: bool,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct SprayResponse {
success: bool,
message: String,
}
#[rocket::post("/v1/spray", data = "<payload>")]
pub async fn set_spray_status(
state: &State<AppState>,
payload: Json<SprayRequest>,
) -> Json<SprayResponse> {
// Call the existing spray_switch function
spary_switch(payload.status);
// Update the spray status in the shared state
{
let mut spray_status = state.spray_status.lock().await;
*spray_status = payload.status;
}
Json(SprayResponse {
success: true,
message: format!("Spray toggled to {}", payload.status),
})
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct SprayStatusResponse {
status: bool,
}
#[rocket::get("/v1/spray")]
pub async fn get_spray_status(
state: &State<AppState>,
) -> Json<SprayStatusResponse> {
let spray_status = state.spray_status.lock().await;
Json(SprayStatusResponse {
status: *spray_status,
})
}