use std::sync::Arc;
use rmcp::ErrorData;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use crate::errors::{McpServerError, map_error};
use crate::state::SessionState;
use crate::tools::actions::AckOutput;
use crate::tools::common::{EmptyInput, current_tab, lookup_frame};
#[derive(Debug, Serialize, JsonSchema, PartialEq, Eq)]
pub struct FrameSummary {
pub id: String,
pub url: String,
pub parent_id: Option<String>,
pub name: Option<String>,
pub is_main: bool,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct FrameListOutput {
pub frames: Vec<FrameSummary>,
}
pub async fn list(
state: Arc<Mutex<SessionState>>,
_: EmptyInput,
) -> Result<FrameListOutput, ErrorData> {
let s = state.lock().await;
let tab = current_tab(&s).await?;
let frames = tab
.frames()
.await
.map_err(|e| map_error(McpServerError::from(e)))?;
let mut out = Vec::with_capacity(frames.len());
for f in &frames {
let url = f.url().await;
out.push(FrameSummary {
id: f.id().to_string(),
url,
parent_id: f.parent_id().map(str::to_string),
name: f.name().map(str::to_string),
is_main: f.is_main(),
});
}
Ok(FrameListOutput { frames: out })
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct FrameGotoInput {
pub frame_id: String,
pub url: String,
}
pub async fn frame_goto(
state: Arc<Mutex<SessionState>>,
input: FrameGotoInput,
) -> Result<AckOutput, ErrorData> {
let s = state.lock().await;
let tab = current_tab(&s).await?;
let frame = lookup_frame(&tab, &input.frame_id).await?;
frame
.goto(&input.url)
.await
.map_err(|e| map_error(McpServerError::from(e)))?;
frame
.wait_for_load()
.await
.map_err(|e| map_error(McpServerError::from(e)))?;
Ok(AckOutput { ok: true })
}
#[cfg(test)]
mod tests {
use super::*;
fn fresh() -> Arc<Mutex<SessionState>> {
Arc::new(Mutex::new(SessionState::new()))
}
#[tokio::test]
async fn list_with_no_browser_suggests_browser_open() {
let err = list(fresh(), EmptyInput {})
.await
.expect_err("must error without an open browser");
assert!(err.message.contains("browser_open"), "msg: {}", err.message);
let data = err.data.as_ref().expect("data populated");
assert_eq!(data["suggested_next"], "browser_open");
}
}