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::common::{current_tab, lookup_frame};
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EvalInput {
pub expression: String,
#[serde(default = "default_await")]
pub await_promise: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub frame_id: Option<String>,
}
const fn default_await() -> bool {
true
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct EvalOutput {
pub value: serde_json::Value,
}
pub async fn evaluate(
state: Arc<Mutex<SessionState>>,
input: EvalInput,
) -> Result<EvalOutput, ErrorData> {
let s = state.lock().await;
let tab = current_tab(&s).await?;
let value: serde_json::Value = if let Some(fid) = input.frame_id.as_deref() {
let frame = lookup_frame(&tab, fid).await?;
frame
.evaluate(&input.expression)
.await
.map_err(|e| map_error(McpServerError::from(e)))?
} else {
tab.evaluate(&input.expression)
.await
.map_err(|e| map_error(McpServerError::from(e)))?
};
Ok(EvalOutput { value })
}
pub async fn evaluate_main(
state: Arc<Mutex<SessionState>>,
input: EvalInput,
) -> Result<EvalOutput, ErrorData> {
let s = state.lock().await;
let tab = current_tab(&s).await?;
let value: serde_json::Value = if let Some(fid) = input.frame_id.as_deref() {
let frame = lookup_frame(&tab, fid).await?;
frame
.evaluate_main(&input.expression)
.await
.map_err(|e| map_error(McpServerError::from(e)))?
} else {
tab.evaluate_main(&input.expression)
.await
.map_err(|e| map_error(McpServerError::from(e)))?
};
Ok(EvalOutput { value })
}
#[cfg(test)]
mod tests {
use super::*;
fn fresh() -> Arc<Mutex<SessionState>> {
Arc::new(Mutex::new(SessionState::new()))
}
#[tokio::test]
async fn evaluate_with_no_browser_suggests_browser_open() {
let err = evaluate(
fresh(),
EvalInput {
expression: "1 + 2".into(),
await_promise: true,
frame_id: None,
},
)
.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");
}
#[tokio::test]
async fn evaluate_main_with_no_browser_suggests_browser_open() {
let err = evaluate_main(
fresh(),
EvalInput {
expression: "document.title".into(),
await_promise: true,
frame_id: None,
},
)
.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");
}
}