feat: faster MCP browsing

This commit is contained in:
zhom committed 2026-10-12 10:59:30 +04:00
1 parent 1e75f179e1
commit aec4870588
6 files changed
+975 -54

No files matched your search

+25
View File
@@ -1045,6 +1045,8 @@ impl ApiServer {
.port();
let v1_routes = build_v1_router();
#[cfg(feature = "e2e")]
let v1_routes = v1_routes.route("/v1/e2e/mcp", axum::routing::post(e2e_mcp_message));
let api = ApiDoc::openapi();
@@ -1101,6 +1103,29 @@ impl ApiServer {
}
}
/// The suites' way into the MCP tool engine, which the app otherwise serves
/// only over the remote bridge. Built into E2E builds only, never published.
#[cfg(feature = "e2e")]
async fn e2e_mcp_message(headers: HeaderMap, body: axum::body::Bytes) -> Response {
use crate::mcp_server::{McpOutcome, McpServer};
let session = headers
.get("mcp-session-id")
.and_then(|value| value.to_str().ok());
match McpServer::instance()
.handle_message_with(session, &body, None)
.await
{
McpOutcome::Body {
body,
new_session_id,
} => Json(serde_json::json!({ "session_id": new_session_id, "body": body })).into_response(),
McpOutcome::Accepted => StatusCode::ACCEPTED.into_response(),
McpOutcome::UnknownSession => StatusCode::NOT_FOUND.into_response(),
McpOutcome::BadRequest => StatusCode::BAD_REQUEST.into_response(),
McpOutcome::RateLimited { .. } => StatusCode::TOO_MANY_REQUESTS.into_response(),
}
}
/// Register every `/v1` handler.
///
/// Pulled out of `start` so a test can build it. Axum panics when two handlers
File diff suppressed because it is too large. Load diff
+4
View File
@@ -37,6 +37,10 @@ pub const TOOLSETS: &[(&str, &[&str])] = &[
"batch_run_profiles",
"batch_stop_profiles",
"navigate",
"go_back",
"scroll",
"press_key",
"wait_for",
"screenshot",
"evaluate_javascript",
"click_element",
+40
View File
@@ -67,6 +67,9 @@ impl NavigationWatch {
};
if method == "Page.loadEventFired" {
self.loaded = true;
} else if method == "Page.frameNavigated" && restored_from_cache(params) {
// A page the back-forward cache keeps comes back whole: no load event.
self.loaded = true;
} else if method == "Page.navigatedWithinDocument" && in_main_frame() {
self.same_document = true;
} else if STARTED.contains(&method) && in_main_frame() {
@@ -98,6 +101,15 @@ impl NavigationWatch {
}
}
/// Whether a `Page.frameNavigated` brought back the top frame from the
/// back-forward cache.
fn restored_from_cache(params: Option<&Value>) -> bool {
params.is_some_and(|p| {
p.get("type").and_then(Value::as_str) == Some("BackForwardCacheRestore")
&& p.pointer("/frame/parentId").is_none()
})
}
/// The top frame's id, from a `Page.getFrameTree` reply.
pub fn main_frame_of(tree: &Value) -> Option<String> {
tree
@@ -130,6 +142,34 @@ mod tests {
assert!(!watch.navigated());
}
#[test]
fn a_page_back_from_the_cache_ends_the_wait_without_a_load_event() {
let start = Instant::now();
let mut watch = NavigationWatch::new(Some("main".to_string()), true);
watch.on_reply(start);
watch.on_event(
"Page.frameNavigated",
Some(
&json!({ "frame": { "id": "ad", "parentId": "main" }, "type": "BackForwardCacheRestore" }),
),
);
assert!(!watch.done(start), "a child frame is not the page");
watch.on_event(
"Page.frameNavigated",
Some(&json!({ "frame": { "id": "main" }, "type": "Navigation" })),
);
assert!(
!watch.done(start),
"a normal navigation still waits for its load"
);
watch.on_event(
"Page.frameNavigated",
Some(&json!({ "frame": { "id": "main" }, "type": "BackForwardCacheRestore" })),
);
assert!(watch.done(start));
assert!(watch.navigated());
}
#[test]
fn a_started_navigation_waits_for_its_load() {
let start = Instant::now();