use serde::Deserialize;
use std::collections::HashMap;
pub type TargetRequest = eoka_protocol::TargetArgs;
pub type FillRequest = eoka_protocol::FillArgs;
pub type SelectRequest = eoka_protocol::SelectArgs;
pub type TypeKeyRequest = eoka_protocol::KeyArgs;
pub type MouseButtonRequest = eoka_protocol::MouseButtonArgs;
pub type MouseMoveRequest = eoka_protocol::MouseMoveArgs;
pub type ScrollRequest = eoka_protocol::TargetArgs;
pub type FindTextRequest = eoka_protocol::TextArgs;
pub type NewTabRequest = eoka_protocol::TabNewArgs;
pub type TabIdRequest = eoka_protocol::TabIdArgs;
pub type SpaNavigateRequest = eoka_protocol::PathStringArgs;
pub type ObserveRequest = eoka_protocol::ObserveArgs;
pub type SnapshotRequest = eoka_protocol::SnapshotArgs;
pub type SaveStateRequest = eoka_protocol::PathArgs;
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct NavigateRequest {
#[schemars(description = "URL to navigate to")]
pub url: String,
#[schemars(
description = "Extra HTTP request headers (e.g. {\"x-middleware-subrequest\": \"middleware\"} for CVE-2025-29927)"
)]
pub headers: Option<HashMap<String, String>>,
#[schemars(description = "Override User-Agent string for this navigation")]
pub user_agent: Option<String>,
#[schemars(description = "Disable CSP enforcement before navigating (useful for XSS testing)")]
pub bypass_csp: Option<bool>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct JsRequest {
#[schemars(description = "JavaScript code to execute (optional if 'file' is provided)")]
pub js: Option<String>,
#[schemars(
description = "Path to a .js file on disk to execute instead of inline code. Saves tokens on repeated/complex scripts."
)]
pub file: Option<String>,
}
pub type FetchRequest = eoka_protocol::FetchArgs;
pub type DownloadStartRequest = eoka_protocol::DownloadStartArgs;
pub type DownloadIdRequest = eoka_protocol::DownloadIdArgs;
pub type DownloadWaitRequest = eoka_protocol::DownloadWaitArgs;
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct SetCookieRequest {
#[schemars(description = "Cookie name")]
pub name: String,
#[schemars(description = "Cookie value")]
pub value: String,
#[schemars(
description = "Cookie domain (e.g. '.example.com'). If omitted, uses current page domain."
)]
pub domain: Option<String>,
#[schemars(description = "Cookie path (default: '/')")]
pub path: Option<String>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct HistoryGoRequest {
#[schemars(description = "History delta: -1 for back, 1 for forward, -2 for back twice, etc.")]
pub delta: i32,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct BatchAction {
#[schemars(description = "Action type: 'click', 'fill', 'type_key'")]
pub action: String,
#[schemars(description = "Target element (for click/fill)")]
pub target: Option<String>,
#[schemars(description = "Text value (for fill/type_key)")]
pub text: Option<String>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct BatchRequest {
#[schemars(description = "Array of actions to execute in sequence")]
pub actions: Vec<BatchAction>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct SolveCaptchaRequest {
#[schemars(description = "Anti-captcha.com API key")]
pub api_key: String,
#[schemars(
description = "Captcha type: 'hcaptcha', 'recaptcha_v2', 'recaptcha_v2_enterprise', 'recaptcha_v3', or 'amazon_waf'"
)]
pub captcha_type: String,
#[schemars(description = "Website/page URL")]
pub website_url: String,
#[schemars(description = "Site key for the captcha")]
pub website_key: String,
#[schemars(description = "Page action (for reCAPTCHA v3)")]
pub page_action: Option<String>,
#[schemars(description = "Minimum score (for reCAPTCHA v3, default 0.3)")]
pub min_score: Option<f32>,
#[schemars(
description = "Optional object passed to grecaptcha.enterprise.render for reCAPTCHA v2 Enterprise"
)]
pub enterprise_payload: Option<serde_json::Value>,
#[schemars(
description = "Optional reCAPTCHA script domain: www.google.com or www.recaptcha.net"
)]
pub api_domain: Option<String>,
#[schemars(description = "AWS WAF iv value from window.gokuProps (required for amazon_waf)")]
pub iv: Option<String>,
#[schemars(
description = "AWS WAF context value from window.gokuProps (required for amazon_waf)"
)]
pub context: Option<String>,
#[schemars(description = "Optional AWS WAF captcha.js URL")]
pub captcha_script: Option<String>,
#[schemars(description = "Optional AWS WAF challenge.js URL")]
pub challenge_script: Option<String>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct DetectCaptchaRequest {
#[schemars(description = "Auto-detect hCaptcha or reCAPTCHA on current page")]
pub auto_detect: Option<bool>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct WaitMsRequest {
#[schemars(description = "Milliseconds to wait (max 30000)")]
pub ms: u64,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ClickInterceptNavRequest {
#[schemars(
description = "Target element. Supports: index (0), text:Submit, placeholder:Email, role:button, css:form button, id:my-btn, or plain text search"
)]
pub target: String,
#[schemars(
description = "How long to wait after click for navigation to be intercepted (ms, default 3000)"
)]
pub wait_ms: Option<u64>,
#[schemars(
description = "If true, allow the navigation to proceed after capturing the URL (default: false — blocks navigation so you can read the page)"
)]
pub allow_nav: Option<bool>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct DeleteCookieRequest {
#[schemars(description = "Cookie name to delete")]
pub name: String,
#[schemars(
description = "Cookie domain (e.g. '.example.com'). If omitted, uses current page domain."
)]
pub domain: Option<String>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct AcceptDialogRequest {
#[schemars(
description = "Text to type for prompt() dialogs (optional, ignored for alert/confirm)"
)]
pub prompt_text: Option<String>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct WaitForTextRequest {
#[schemars(description = "Text substring to wait for (case-insensitive)")]
pub text: String,
#[schemars(description = "Timeout in milliseconds (default: 10000)")]
pub timeout_ms: Option<u64>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct WaitNetworkIdleRequest {
#[schemars(description = "Milliseconds without requests to consider idle (default: 500)")]
pub idle_ms: Option<u64>,
#[schemars(description = "Maximum wait time in milliseconds (default: 10000)")]
pub timeout_ms: Option<u64>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct StorageKeyRequest {
#[schemars(description = "Storage key to read")]
pub key: String,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct StorageSetRequest {
#[schemars(description = "Storage key")]
pub key: String,
#[schemars(description = "Value to store")]
pub value: String,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ConsoleRequest {
#[schemars(description = "Clear the console buffer after reading (default: false)")]
pub clear: Option<bool>,
#[schemars(
description = "Filter by level: 'log', 'warn', 'error', 'info', 'debug' (default: all)"
)]
pub level: Option<String>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ErrorsRequest {
#[schemars(description = "Clear the errors buffer after reading (default: false)")]
pub clear: Option<bool>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct LoadStateRequest {
#[schemars(description = "File path to load state from (JSON). Absolute path required.")]
pub path: String,
#[schemars(
description = "Navigate to the saved URL after loading state (default: true). Set false to restore state then navigate somewhere else."
)]
pub navigate: Option<bool>,
}
#[cfg(test)]
mod tests {
use eoka_protocol::{input_schema_for_cmd, operation_by_cmd};
use super::{
FillRequest, MouseButtonRequest, MouseMoveRequest, NewTabRequest, ObserveRequest,
SaveStateRequest, SelectRequest, SnapshotRequest, SpaNavigateRequest, TabIdRequest,
TargetRequest, TypeKeyRequest,
};
#[test]
fn shared_mcp_protocol_operations_are_cataloged() {
let shared_commands = [
"snapshot",
"click",
"fill",
"select",
"key",
"mouse_down",
"mouse_move",
"mouse_up",
"key_down",
"key_up",
"release_all_inputs",
"scroll",
"find",
"tab_new",
"tab_switch",
"tab_close",
"spa_navigate",
"observe",
"save_state",
];
for command in shared_commands {
assert!(
operation_by_cmd(command).is_some(),
"MCP shared request type is not represented in protocol catalog: {command}"
);
}
}
#[test]
fn shared_mcp_protocol_request_schemas_match_catalog() {
assert_eq!(
serde_json::to_value(schemars::schema_for!(TargetRequest)).unwrap(),
input_schema_for_cmd("click")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(FillRequest)).unwrap(),
input_schema_for_cmd("fill")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(SelectRequest)).unwrap(),
input_schema_for_cmd("select")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(TypeKeyRequest)).unwrap(),
input_schema_for_cmd("key")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(MouseButtonRequest)).unwrap(),
input_schema_for_cmd("mouse_down")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(MouseMoveRequest)).unwrap(),
input_schema_for_cmd("mouse_move")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(NewTabRequest)).unwrap(),
input_schema_for_cmd("tab_new")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(TabIdRequest)).unwrap(),
input_schema_for_cmd("tab_switch")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(SpaNavigateRequest)).unwrap(),
input_schema_for_cmd("spa_navigate")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(ObserveRequest)).unwrap(),
input_schema_for_cmd("observe")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(SnapshotRequest)).unwrap(),
input_schema_for_cmd("snapshot")
);
assert_eq!(
serde_json::to_value(schemars::schema_for!(SaveStateRequest)).unwrap(),
input_schema_for_cmd("save_state")
);
}
}