Skip to content

Commit e744bba

Browse files
authored
Merge pull request #291 from hmishra2250/feat/interact-standalone-url
feat(interact): allow firecrawl_interact to open a session from a url
2 parents d1796b6 + d5cf6cb commit e744bba

3 files changed

Lines changed: 75 additions & 16 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,14 +344,14 @@ Use this guide to select the right tool for your task:
344344
- **If you want to search the web for info:** use **search**
345345
- **If you need complex research across multiple unknown sources:** use **agent**
346346
- **If you want to analyze a whole site or section:** use **crawl** (with limits!)
347-
- **If you need interactive browser automation** (click, type, navigate): use **scrape** + **interact**
347+
- **If you need interactive browser automation** (click, type, navigate): use **interact** with a URL for a fresh page, or **scrape** + **interact** when you already scraped the page or need tighter scrape control
348348

349349
### Quick Reference Table
350350

351351
| Tool | Best for | Returns |
352352
| ------------ | ---------------------------------------------- | ------------------------------ |
353353
| scrape | Single page content | JSON (preferred) or markdown |
354-
| interact | Interact with a scraped page | Execution result |
354+
| interact | Interact with a URL or scraped page | Execution result + scrapeId for URL mode |
355355
| batch_scrape | Multiple known URLs | JSON (preferred) or markdown[] |
356356
| map | Discovering URLs on a site | URL[] |
357357
| crawl | Multi-page extraction (with limits) | markdown/html[] |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firecrawl-mcp",
3-
"version": "3.21.4",
3+
"version": "3.22.0",
44
"description": "MCP server for Firecrawl — search, scrape, and interact with the web. Supports both cloud and self-hosted instances. Features include web search, scraping, page interaction, batch processing, and LLM-powered content analysis.",
55
"type": "module",
66
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",

src/index.ts

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2192,24 +2192,28 @@ server.addTool({
21922192
destructiveHint: false, // Transient page interactions only; does not delete monitors, jobs, or external sites.
21932193
},
21942194
description: `
2195-
Interact with a previously scraped page in a live browser session. Scrape a page first with firecrawl_scrape, then use the returned scrapeId to click buttons, fill forms, extract dynamic content, or navigate deeper.
2195+
Interact with a page in a live browser session: click buttons, fill forms, extract dynamic content, or navigate deeper.
21962196
21972197
**Best for:** Multi-step workflows on a single page — searching a site, clicking through results, filling forms, extracting data that requires interaction.
2198-
**Requires:** A scrapeId from a previous firecrawl_scrape call (found in the metadata of the scrape response).
2198+
**Two ways to target a page:**
2199+
- Pass a \`url\` to interact directly. The session is opened for you in one call (use this for a fresh page).
2200+
- Pass a \`scrapeId\` from a previous firecrawl_scrape to reuse that already-loaded page (cheaper when you just scraped it).
21992201
22002202
**Arguments:**
2201-
- scrapeId: The scrape job ID from a previous scrape (required)
2203+
- url: Page to interact with; opens a session for you (use this OR scrapeId)
2204+
- scrapeId: Scrape job ID from a previous scrape, found in its metadata (use this OR url)
22022205
- prompt: Natural language instruction describing the action to take (use this OR code)
22032206
- code: Code to execute in the browser session (use this OR prompt)
22042207
- language: "bash", "python", or "node" (optional, defaults to "node", only used with code)
2205-
- timeout: Execution timeout in seconds, 1-300 (optional, defaults to 30)
2208+
- timeout: Interact execution timeout in seconds, 1-300 (optional, defaults to 30)
2209+
- scrapeOptions: Optional scrape controls used only with url mode, such as waitFor, maxAge, proxy, or zeroDataRetention
22062210
2207-
**Usage Example (prompt):**
2211+
**Usage Example (prompt, direct via url):**
22082212
\`\`\`json
22092213
{
22102214
"name": "firecrawl_interact",
22112215
"arguments": {
2212-
"scrapeId": "scrape-id-from-previous-scrape",
2216+
"url": "https://example.com/products",
22132217
"prompt": "Click on the first product and tell me its price"
22142218
}
22152219
}
@@ -2230,31 +2234,86 @@ Interact with a previously scraped page in a live browser session. Scrape a page
22302234
`,
22312235
parameters: z
22322236
.object({
2233-
scrapeId: z.string(),
2234-
prompt: z.string().optional(),
2235-
code: z.string().optional(),
2237+
scrapeId: z.string().trim().min(1).optional(),
2238+
url: z.string().trim().url().optional(),
2239+
prompt: z.string().trim().min(1).optional(),
2240+
code: z.string().trim().min(1).optional(),
22362241
language: z.enum(['bash', 'python', 'node']).optional(),
22372242
timeout: z.number().min(1).max(300).optional(),
2243+
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional(),
2244+
})
2245+
.refine((data) => Boolean(data.scrapeId) !== Boolean(data.url), {
2246+
message:
2247+
"Provide either 'url' (interact directly) or 'scrapeId' (reuse a previous scrape), not both.",
2248+
})
2249+
.refine((data) => !data.scrapeOptions || Boolean(data.url), {
2250+
message: "scrapeOptions can only be used with 'url' mode.",
22382251
})
22392252
.refine((data) => data.code || data.prompt, {
22402253
message: "Either 'code' or 'prompt' must be provided.",
22412254
}),
22422255
execute: async (args: unknown, { session, log }): Promise<string> => {
22432256
const client = getClient(session);
2244-
const { scrapeId, prompt, code, language, timeout } = args as {
2245-
scrapeId: string;
2257+
const {
2258+
scrapeId: providedScrapeId,
2259+
url,
2260+
prompt,
2261+
code,
2262+
language,
2263+
timeout,
2264+
scrapeOptions,
2265+
} = args as {
2266+
scrapeId?: string;
2267+
url?: string;
22462268
prompt?: string;
22472269
code?: string;
22482270
language?: 'bash' | 'python' | 'node';
22492271
timeout?: number;
2272+
scrapeOptions?: Record<string, unknown>;
22502273
};
2251-
log.info('Interacting with scraped page', { scrapeId });
2274+
// No scrapeId means the caller passed a url: scrape it first to open the
2275+
// session, then interact. One tool call instead of scrape + interact.
2276+
let scrapeId = providedScrapeId;
2277+
const openedFromUrl = !scrapeId;
2278+
if (openedFromUrl) {
2279+
log.info('Opening interact session from url', { url });
2280+
const cleanedScrapeOptions = removeEmptyTopLevel(scrapeOptions ?? {});
2281+
const scraped = await client.scrape(String(url), {
2282+
...cleanedScrapeOptions,
2283+
origin: ORIGIN,
2284+
} as any);
2285+
scrapeId = (scraped as any)?.metadata?.scrapeId;
2286+
if (!scrapeId) {
2287+
return asText({
2288+
error:
2289+
'Could not open an interact session: the scrape did not return a scrapeId. Try firecrawl_scrape first, then pass its scrapeId.',
2290+
url,
2291+
});
2292+
}
2293+
}
2294+
if (!scrapeId) {
2295+
return asText({
2296+
error: 'Could not open an interact session: missing scrapeId.',
2297+
url,
2298+
});
2299+
}
2300+
const activeScrapeId = scrapeId;
2301+
log.info('Interacting with page', { scrapeId: activeScrapeId });
22522302
const interactArgs: Record<string, unknown> = { origin: ORIGIN };
22532303
if (prompt) interactArgs.prompt = prompt;
22542304
if (code) interactArgs.code = code;
22552305
if (language) interactArgs.language = language;
22562306
if (timeout != null) interactArgs.timeout = timeout;
2257-
const res = await client.interact(scrapeId, interactArgs as any);
2307+
const res = await client.interact(activeScrapeId, interactArgs as any);
2308+
if (openedFromUrl && res && typeof res === 'object' && !Array.isArray(res)) {
2309+
return asText({
2310+
...(res as unknown as Record<string, unknown>),
2311+
scrapeId: activeScrapeId,
2312+
});
2313+
}
2314+
if (openedFromUrl) {
2315+
return asText({ scrapeId: activeScrapeId, result: res });
2316+
}
22582317
return asText(res);
22592318
},
22602319
});

0 commit comments

Comments
 (0)