From 2061df7d9aa25182a6fc63a0d5a5340812e5030c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:28:42 +0000 Subject: [PATCH 1/2] feat: implement cancel crawl functionality in MCP server - Add CANCEL_CRAWL_TOOL with proper input schema and description - Add firecrawl_cancel_crawl handler with retry logic and error handling - Add type guard function isCancelCrawlOptions for input validation - Add test coverage for cancel crawl functionality - Follow existing patterns for consistency Co-Authored-By: Nick --- src/index.test.ts | 38 +++++++++++++++++++++++++++++ src/index.ts | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index 1ffe829..aa73eb9 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -76,6 +76,7 @@ interface MockFirecrawlClient { checkBatchScrapeStatus(id: string): Promise; asyncCrawlUrl(url: string, options?: any): Promise; checkCrawlStatus(id: string): Promise; + cancelCrawl(id: string): Promise<{ success: boolean; error?: string }>; mapUrl(url: string, options?: any): Promise<{ links: string[] }>; } @@ -257,6 +258,27 @@ describe('Firecrawl Tool Tests', () => { }); }); + // Test cancel crawl functionality + test('should handle cancel crawl request', async () => { + const crawlId = 'test-crawl-id'; + + mockClient.cancelCrawl.mockResolvedValueOnce({ + success: true, + }); + + const response = await requestHandler({ + method: 'call_tool', + params: { + name: 'firecrawl_cancel_crawl', + arguments: { id: crawlId }, + }, + }); + + expect(response.isError).toBe(false); + expect(response.content[0].text).toContain('cancelled successfully'); + expect(mockClient.cancelCrawl).toHaveBeenCalledWith(crawlId); + }); + // Test error handling test('should handle API errors', async () => { const url = 'https://example.com'; @@ -371,6 +393,22 @@ async function handleRequest( }; } + case 'firecrawl_cancel_crawl': { + const response = await client.cancelCrawl(args.id); + if (!response.success) { + throw new Error(response.error || 'Failed to cancel crawl'); + } + return { + content: [ + { + type: 'text', + text: `Crawl job ${args.id} has been cancelled successfully.`, + }, + ], + isError: false, + }; + } + default: throw new Error(`Unknown tool: ${name}`); } diff --git a/src/index.ts b/src/index.ts index ff2550d..dfe9d3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -412,6 +412,35 @@ Check the status of a crawl job. }, }; +const CANCEL_CRAWL_TOOL: Tool = { + name: 'firecrawl_cancel_crawl', + description: ` +Cancel a running crawl job. + +**Best for:** Stopping crawl operations that are taking too long or are no longer needed. +**Usage Example:** +\`\`\`json +{ + "name": "firecrawl_cancel_crawl", + "arguments": { + "id": "550e8400-e29b-41d4-a716-446655440000" + } +} +\`\`\` +**Returns:** Confirmation that the crawl job has been cancelled. +`, + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Crawl job ID to cancel', + }, + }, + required: ['id'], + }, +}; + const SEARCH_TOOL: Tool = { name: 'firecrawl_search', description: ` @@ -818,6 +847,15 @@ function isStatusCheckOptions(args: unknown): args is StatusCheckOptions { ); } +function isCancelCrawlOptions(args: unknown): args is StatusCheckOptions { + return ( + typeof args === 'object' && + args !== null && + 'id' in args && + typeof (args as { id: unknown }).id === 'string' + ); +} + function isSearchOptions(args: unknown): args is SearchOptions { return ( typeof args === 'object' && @@ -965,6 +1003,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ MAP_TOOL, CRAWL_TOOL, CHECK_CRAWL_STATUS_TOOL, + CANCEL_CRAWL_TOOL, SEARCH_TOOL, EXTRACT_TOOL, DEEP_RESEARCH_TOOL, @@ -1155,6 +1194,28 @@ ${ }; } + case 'firecrawl_cancel_crawl': { + if (!isCancelCrawlOptions(args)) { + throw new Error('Invalid arguments for firecrawl_cancel_crawl'); + } + const response = await withRetry( + async () => client.cancelCrawl(args.id), + 'cancel crawl operation' + ); + if (!response.success) { + throw new Error(response.error || 'Failed to cancel crawl'); + } + return { + content: [ + { + type: 'text', + text: trimResponseText(`Crawl job ${args.id} has been cancelled successfully.`), + }, + ], + isError: false, + }; + } + case 'firecrawl_search': { if (!isSearchOptions(args)) { throw new Error('Invalid arguments for firecrawl_search'); From eefe2bdbd27c5d35a759c3e03760f9c25381d4df Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:28:52 +0000 Subject: [PATCH 2/2] chore: update package-lock.json from npm operations Co-Authored-By: Nick --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index de77fb4..fea7c86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "firecrawl-mcp", - "version": "1.11.0", + "version": "1.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firecrawl-mcp", - "version": "1.11.0", + "version": "1.12.0", "license": "MIT", "dependencies": { "@mendable/firecrawl-js": "^1.19.0",