feat(server): sous-commande idea --serve avec serveur HTTP sécurisé pairing+cookie (#13)
Lot B3 du chantier server/client mode : socle serveur sécurisé exposant le cœur backend en mode client/serveur, sans PTY (reporté B5). - crates/app-tauri/src/server.rs : sous-commande --serve, serveur HTTP sécurisé — /api/invoke sur allowlist, /api/pair → cookie de session, Origin strict, refus du secret en query, gate de bind. 16 tests dont les 2 refus de sécurité (pairing_rejects_wrong_code, invoke_rejects_invalid_session_cookie). - crates/app-tauri/src/lib.rs : câblage de la sous-commande --serve. - crates/app-tauri/src/commands.rs : adaptations. - crates/app-tauri/Cargo.toml : deps bytes/cookie/http/http-body-util, feature tokio net. Cargo.lock synchronisé. Validé : cargo check --workspace vert, app-tauri 267 tests verts, cœur backend agnostique confirmé, desktop non régressé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -26,14 +26,18 @@ infrastructure = { workspace = true }
|
||||
backend = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
# `io-std` (on top of the workspace features) gives the headless `mcp-server`
|
||||
# bridge access to `tokio::io::{stdin,stdout}` without widening tokio elsewhere.
|
||||
tokio = { workspace = true, features = ["io-std", "rt"] }
|
||||
# `io-std` gives the headless `mcp-server` bridge access to
|
||||
# `tokio::io::{stdin,stdout}`; `net` is used only by the `--serve` HTTP adapter.
|
||||
tokio = { workspace = true, features = ["io-std", "rt", "net"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
base64 = "0.22"
|
||||
bytes = "1.11"
|
||||
cookie = "0.18"
|
||||
http = "1.4"
|
||||
http-body-util = "0.1"
|
||||
# `AppAgentResumer` implements the application's async `AgentResumer` port (LS7).
|
||||
async-trait = { workspace = true }
|
||||
# Cross-OS local IPC for the MCP loopback transport (M5a): Unix domain socket
|
||||
|
||||
@ -113,6 +113,17 @@ pub async fn create_project(
|
||||
pub async fn open_project(
|
||||
project_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ProjectDto, ErrorDto> {
|
||||
open_project_for_adapter(project_id, &state).await
|
||||
}
|
||||
|
||||
/// Shared `open_project` adapter body used by desktop IPC and the HTTP server.
|
||||
///
|
||||
/// Keeps the non-presentation side effects attached to project opening in one
|
||||
/// place while B3 adds a second driving adapter.
|
||||
pub(crate) async fn open_project_for_adapter(
|
||||
project_id: String,
|
||||
state: &AppState,
|
||||
) -> Result<ProjectDto, ErrorDto> {
|
||||
let id = parse_project_id(&project_id)?;
|
||||
let output = state
|
||||
|
||||
@ -21,6 +21,7 @@ pub mod mcp_bridge;
|
||||
pub mod mcp_endpoint;
|
||||
pub mod openai_tools;
|
||||
pub mod pty;
|
||||
pub mod server;
|
||||
pub mod state;
|
||||
pub mod stream;
|
||||
pub mod tickets;
|
||||
@ -42,14 +43,17 @@ use state::AppState;
|
||||
/// The `argv[1]` subcommand token that switches the binary into the headless
|
||||
/// `mcp-server` **bridge** mode (cadrage v5 §1.3) instead of launching Tauri.
|
||||
pub const MCP_SERVER_SUBCOMMAND: &str = "mcp-server";
|
||||
/// The `argv[1]` subcommand token that starts the secure HTTP server adapter.
|
||||
pub const SERVE_SUBCOMMAND: &str = "--serve";
|
||||
|
||||
/// Process entry point: routes `argv` **before** anything Tauri/WebKit is touched.
|
||||
///
|
||||
/// When invoked as `<exe> mcp-server …` (an MCP CLI spawned us from the injected
|
||||
/// `.mcp.json` declaration), we run the **stdio↔loopback bridge** headless and
|
||||
/// **never** initialise the webview — see [`mcp_bridge::run_mcp_bridge`]. Any other
|
||||
/// invocation is the normal IDE launch: [`run`] (which blocks until the window
|
||||
/// closes and then exits the process itself).
|
||||
/// **never** initialise the webview — see [`mcp_bridge::run_mcp_bridge`]. When
|
||||
/// invoked as `<exe> --serve …`, we run the secure HTTP adapter headless. Any
|
||||
/// other invocation is the normal IDE launch: [`run`] (which blocks until the
|
||||
/// window closes and then exits the process itself).
|
||||
///
|
||||
/// Returns the [`ExitCode`] for the bridge path; the normal path does not return.
|
||||
#[must_use]
|
||||
@ -61,6 +65,11 @@ pub fn dispatch() -> ExitCode {
|
||||
let rest: Vec<String> = args.map(|a| a.to_string_lossy().into_owned()).collect();
|
||||
return mcp_bridge::run_mcp_bridge(rest);
|
||||
}
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
if args.next().is_some_and(|a| a == *SERVE_SUBCOMMAND) {
|
||||
let rest: Vec<String> = args.map(|a| a.to_string_lossy().into_owned()).collect();
|
||||
return server::run_from_args(rest);
|
||||
}
|
||||
|
||||
run();
|
||||
ExitCode::SUCCESS
|
||||
|
||||
1130
crates/app-tauri/src/server.rs
Normal file
1130
crates/app-tauri/src/server.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user