firecrawl_mcp/controller/
crawl.rs1use anyhow::Result;
2use async_claude::define_tool;
3use firecrawl_sdk::{
4 batch_scrape::Webhook,
5 crawl::CrawlUrlInput,
6 scrape::{ScrapeFormats, ScrapeOptions},
7};
8use rmcp::{Error, handler::server::tool::parse_json_object, model::JsonObject};
9
10use super::FirecrawlMCP;
11
12pub const CRAWL_TOOL_NAME: &str = "firecrawl_crawl";
13pub const CRAWL_TOOL_DESCRIPTION: &str = "Crawl multiple pages from a starting URL. Supports depth control, path filtering, and webhook notifications.";
14define_tool!(
15 FIRECRAWL_CRAWL,
16 CRAWL_TOOL_NAME,
17 CRAWL_TOOL_DESCRIPTION,
18 CrawlUrlInput
19);
20
21impl FirecrawlMCP {
22 pub async fn crawl(&self, input: JsonObject) -> Result<String, Error> {
23 let mut options = parse_json_object::<CrawlUrlInput>(input)?;
24
25 if options.webhook.is_none() {
26 options.webhook = Some(Webhook::dummy());
27 }
28
29 match &mut options.options.scrape_options {
31 Some(scrape_options) => {
32 scrape_options.formats = Some(vec![ScrapeFormats::Markdown]);
33 }
34 None => {
35 options.options.scrape_options = Some(ScrapeOptions {
36 formats: Some(vec![ScrapeFormats::Markdown]),
37 ..Default::default()
38 });
39 }
40 }
41
42 let results = self
43 .client
44 .crawl_url(
45 options.url,
46 Some(options.options),
47 options.webhook.unwrap(),
48 options.poll_interval,
49 None,
50 )
51 .await
52 .map_err(|e| Error::internal_error(e.to_string(), None))?;
53
54 let formatted = results
55 .data
56 .iter()
57 .map(|d| {
58 format!(
59 "URL: {}\nTitle: {}\nContent: {}",
60 d.metadata.source_url,
61 d.metadata.title.as_ref().unwrap_or(&"".to_string()),
62 d.markdown.as_ref().unwrap_or(&"".to_string())
63 )
64 })
65 .collect::<Vec<_>>()
66 .join("\n\n");
67
68 Ok(formatted)
69 }
70}