ferridriver-mcp 0.4.0

ferridriver MCP server library -- browser automation via Model Context Protocol
Documentation
use crate::params::DiagnosticsParams;
use crate::server::{McpServer, sess};
use rmcp::{
  ErrorData,
  handler::server::wrapper::Parameters,
  model::{CallToolResult, Content},
  tool, tool_router,
};
use std::fmt::Write;

#[tool_router(router = network_router, vis = "pub")]
impl McpServer {
  #[tool(
    name = "diagnostics",
    description = "Page diagnostics. Types: console (log/warn/error messages), network (HTTP requests since load), trace_start (begin perf tracing), trace_stop (end tracing + metrics)."
  )]
  async fn diagnostics(&self, Parameters(p): Parameters<DiagnosticsParams>) -> Result<CallToolResult, ErrorData> {
    let s = sess(p.session.as_opt());
    match p.r#type.as_str() {
      "console" => {
        let _guard = self.session_guard(s).await;
        let handles = self
          .state
          .log_handles_for(s)
          .await
          .ok_or_else(|| Self::err(format!("Context '{s}' not found")))?;
        let limit = p.limit.unwrap_or(50);
        let level = p.level.as_deref();
        let log = handles.console.read().await;
        let msgs: Vec<serde_json::Value> = log
          .iter()
          .filter(|m| level.is_none_or(|l| l == "all" || m.type_str() == l))
          .rev()
          .take(limit)
          .map(|m| {
            serde_json::json!({
              "type": m.type_str(),
              "text": m.text(),
            })
          })
          .collect::<Vec<_>>()
          .into_iter()
          .rev()
          .collect();
        drop(log);
        Ok(CallToolResult::success(vec![Content::text(
          serde_json::to_string_pretty(&msgs).unwrap_or_default(),
        )]))
      },
      "network" => {
        let _guard = self.session_guard(s).await;
        let handles = self
          .state
          .log_handles_for(s)
          .await
          .ok_or_else(|| Self::err(format!("Context '{s}' not found")))?;
        let limit = p.limit.unwrap_or(50);
        let log = handles.network.read().await;
        let reqs: Vec<_> = log
          .iter()
          .rev()
          .take(limit)
          .cloned()
          .collect::<Vec<_>>()
          .into_iter()
          .rev()
          .collect();
        drop(log);
        let mut snapshots = Vec::with_capacity(reqs.len());
        for req in &reqs {
          snapshots.push(req.to_diagnostic_json().await);
        }
        Ok(CallToolResult::success(vec![Content::text(
          serde_json::to_string_pretty(&snapshots).unwrap_or_default(),
        )]))
      },
      "trace_start" => {
        let _guard = self.session_guard(s).await;
        let page = Box::pin(self.page(s)).await?;
        page.start_tracing().await.map_err(Self::err)?;
        Ok(CallToolResult::success(vec![Content::text("Trace started.")]))
      },
      "trace_stop" => {
        let _guard = self.session_guard(s).await;
        let page = Box::pin(self.page(s)).await?;
        page.stop_tracing().await.map_err(Self::err)?;
        let metrics = page.metrics().await.map_err(Self::err)?;
        let mut out = String::from("Trace stopped.\n\n### Performance Metrics\n");
        for m in &metrics {
          if m.value > 0.0 {
            let _ = writeln!(out, "- {}: {:.2}", m.name, m.value);
          }
        }
        Ok(CallToolResult::success(vec![Content::text(out)]))
      },
      other => Err(Self::err(format!(
        "Unknown type '{other}'. Use: console, network, trace_start, trace_stop."
      ))),
    }
  }
}