AutomationFlowsAI & RAG › Web Research Assistant: Automated Search & Scraping with Gemini AI and…

Web Research Assistant: Automated Search & Scraping with Gemini AI and…

Original n8n title: Web Research Assistant: Automated Search & Scraping with Gemini AI and Spreadsheet Reports

Byfranck fambou @franck-f on n8n.io

⚠️ IMPORTANT: This template requires self-hosted n8n hosting due to the use of community nodes (MCP tools). It will not work on n8n Cloud. Make sure you have access to a self-hosted n8n instance before using this template.

Chat trigger trigger★★★★☆ complexityAI-powered23 nodesChat TriggerMemory Buffer WindowGoogle Gemini ChatN8N Nodes McpHTTP RequestAgentGoogle Sheets
AI & RAG Trigger: Chat trigger Nodes: 23 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #8218 — we link there as the canonical source.

This workflow follows the Agent → Chat Trigger recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "SjiPE2lVXmmyh7Ca",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "multi-source scraping",
  "tags": [],
  "nodes": [
    {
      "id": "bf0013da-2066-45de-8833-fa2927728f00",
      "name": "When chat message received",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "position": [
        -288,
        0
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "9fe3e0e0-f5ac-4018-90b7-f2e4fc7b207d",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        224,
        144
      ],
      "parameters": {
        "contextWindowLength": 30
      },
      "typeVersion": 1.3
    },
    {
      "id": "c9e95768-ea7c-43f0-b6fd-a824fc64d60d",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        80,
        144
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "cd7be0c8-f708-42f8-8411-a3622c25a0bf",
      "name": "Firecrawl list",
      "type": "n8n-nodes-mcp.mcpClientTool",
      "position": [
        672,
        352
      ],
      "parameters": {},
      "credentials": {
        "mcpClientApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4cdda777-345c-4129-9ada-1a9c01b9a104",
      "name": "Firecrawl execute",
      "type": "n8n-nodes-mcp.mcpClientTool",
      "position": [
        672,
        464
      ],
      "parameters": {
        "toolName": "={{ \n$fromAI(\"tool\", \"the selected tool to use\")\n}}",
        "operation": "executeTool",
        "toolParameters": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Tool_Parameters', ``, 'json') }}"
      },
      "credentials": {
        "mcpClientApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "35a550c1-8fb7-40c3-af16-bb059c6d1947",
      "name": "Brave list",
      "type": "n8n-nodes-mcp.mcpClientTool",
      "position": [
        768,
        352
      ],
      "parameters": {},
      "credentials": {
        "mcpClientApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ecd7944c-0de2-4447-b640-7a7caaa30705",
      "name": "Brave execute",
      "type": "n8n-nodes-mcp.mcpClientTool",
      "position": [
        768,
        464
      ],
      "parameters": {
        "toolName": "={{\n$fromAI(\"tool\", \"the selected tool to use\")\n}}",
        "operation": "executeTool",
        "toolParameters": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Tool_Parameters', ``, 'json') }}"
      },
      "credentials": {
        "mcpClientApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3041cd71-2131-4cf5-b21b-7b7decc0a4bb",
      "name": "Apify list",
      "type": "n8n-nodes-mcp.mcpClientTool",
      "position": [
        864,
        464
      ],
      "parameters": {},
      "credentials": {
        "mcpClientApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1ba3c633-998b-4ab2-83c1-c6dd0e7c6679",
      "name": "Apify execute",
      "type": "n8n-nodes-mcp.mcpClientTool",
      "position": [
        864,
        352
      ],
      "parameters": {
        "toolName": "={{ $fromAI(\"tool\", \"the selected tool to use\") }}",
        "operation": "executeTool",
        "toolParameters": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Tool_Parameters', ``, 'json') }}"
      },
      "credentials": {
        "mcpClientApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "73031a7a-6338-4683-a6e3-efee3819f89e",
      "name": "Data Enrichment Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1296,
        0
      ],
      "parameters": {
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.spreadsheetId }}/values:batchUpdate",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"valueInputOption\": \"USER_ENTERED\",\n  \"data\": {{ JSON.stringify($json.valueRanges) }}\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "9a7f6cbe-a132-4fef-9af8-b9851227faf6",
      "name": "Gemini Research Orchestrator",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        208,
        0
      ],
      "parameters": {
        "options": {
          "systemMessage": "You are a helpful assistant\n\nUse Brave to search into the web\nUse Firecrawl to scrape websites\nUse Apify to scrape websites\n\nthe process is to use the tool node first then the execute node.\n\nAlways refer to the list of tools before attempting to access or use the execute tools"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "acf4a537-627a-4d20-8b02-215f8075900b",
      "name": "Create Research Report",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        704,
        0
      ],
      "parameters": {
        "title": "scrapped ",
        "options": {},
        "resource": "spreadsheet"
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "13e00e06-9b7e-4fb2-bbbd-9e7211de9e96",
      "name": "Format Research Data",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        0
      ],
      "parameters": {
        "jsCode": "// Version s\u00e9curis\u00e9e qui g\u00e8re les feuilles existantes\nconst agentOutput = $('Gemini Research Orchestrator').item.json.output;\nconst spreadsheetId = $('Create Research Report').item.json.spreadsheetId;\n\n// Fonction pour parser le markdown (identique)\nfunction parseAgentOutput(output) {\n  const lines = output.split('\\n').filter(line => line.trim() !== '');\n  const data = {\n    title: '',\n    categories: [],\n    jobs: []\n  };\n  \n  let currentSection = '';\n  let inTable = false;\n  let tableHeaders = [];\n  \n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i].trim();\n    \n    if (line.startsWith('Voici les donn\u00e9es extraites')) {\n      data.title = line.replace(/\\*\\*/g, '').replace('Voici les donn\u00e9es extraites du site ', '').replace(/[`:`]/g, '');\n    }\n    \n    if (line.startsWith('**') && line.endsWith(':**')) {\n      currentSection = line.replace(/\\*\\*/g, '').replace(':', '');\n      inTable = false;\n    }\n    \n    if (currentSection === 'Cat\u00e9gories d\\'emploi' && line.startsWith('*   ')) {\n      data.categories.push(line.replace('*   ', ''));\n    }\n    \n    if (line.startsWith('|') && !inTable) {\n      const headers = line.split('|').map(h => h.trim()).filter(h => h !== '');\n      if (headers.length > 0) {\n        tableHeaders = headers;\n        inTable = true;\n        i++;\n        continue;\n      }\n    }\n    \n    if (inTable && line.startsWith('|')) {\n      const cells = line.split('|').map(c => c.trim()).filter(c => c !== '');\n      if (cells.length === tableHeaders.length) {\n        const job = {};\n        tableHeaders.forEach((header, index) => {\n          job[header] = cells[index] || '';\n        });\n        data.jobs.push(job);\n      }\n    }\n  }\n  \n  return data;\n}\n\n// Fonction pour cr\u00e9er ou mettre \u00e0 jour une feuille\nfunction createOrUpdateSheet(sheetName, sheetId, properties = {}) {\n  if (sheetId === 0) {\n    // Pour la feuille par d\u00e9faut (ID 0), on la met \u00e0 jour\n    return {\n      updateSheetProperties: {\n        properties: {\n          sheetId: sheetId,\n          title: sheetName,\n          gridProperties: {\n            rowCount: 1000,\n            columnCount: 26\n          },\n          ...properties\n        },\n        fields: 'title,tabColor,gridProperties'\n      }\n    };\n  } else {\n    // Pour les nouvelles feuilles, on les cr\u00e9e\n    return {\n      addSheet: {\n        properties: {\n          sheetId: sheetId,\n          title: sheetName,\n          gridProperties: {\n            rowCount: 1000,\n            columnCount: 26\n          },\n          ...properties\n        }\n      }\n    };\n  }\n}\n\n// Fonction pour ajouter des donn\u00e9es dans une feuille\nfunction addSheetData(sheetName, range, values, majorDimension = 'ROWS') {\n  return {\n    range: `'${sheetName}'!${range}`,\n    majorDimension: majorDimension,\n    values: values\n  };\n}\n\n// Fonction pour formater les cellules\nfunction formatCells(sheetId, range, format) {\n  return {\n    repeatCell: {\n      range: {\n        sheetId: sheetId,\n        startRowIndex: range.startRow,\n        endRowIndex: range.endRow,\n        startColumnIndex: range.startCol,\n        endColumnIndex: range.endCol\n      },\n      cell: {\n        userEnteredFormat: format\n      },\n      fields: 'userEnteredFormat'\n    }\n  };\n}\n\n// Fonction pour obtenir les styles de formatage\nfunction getTextFormat(style) {\n  const formats = {\n    'TITLE': {\n      textFormat: {\n        bold: true,\n        fontSize: 16,\n        foregroundColor: { red: 0.1, green: 0.1, blue: 0.1 }\n      },\n      horizontalAlignment: 'CENTER',\n      backgroundColor: { red: 0.85, green: 0.85, blue: 0.95 }\n    },\n    'SECTION_HEADER': {\n      textFormat: {\n        bold: true,\n        fontSize: 14,\n        foregroundColor: { red: 0.2, green: 0.2, blue: 0.2 }\n      },\n      backgroundColor: { red: 0.9, green: 0.9, blue: 0.9 }\n    },\n    'TABLE_HEADER': {\n      textFormat: {\n        bold: true,\n        fontSize: 11,\n        foregroundColor: { red: 1, green: 1, blue: 1 }\n      },\n      backgroundColor: { red: 0.2, green: 0.4, blue: 0.8 },\n      horizontalAlignment: 'CENTER',\n      borders: {\n        top: { style: 'SOLID', width: 2 },\n        bottom: { style: 'SOLID', width: 2 },\n        left: { style: 'SOLID', width: 1 },\n        right: { style: 'SOLID', width: 1 }\n      }\n    },\n    'TABLE_CELL': {\n      textFormat: {\n        fontSize: 10,\n        foregroundColor: { red: 0.1, green: 0.1, blue: 0.1 }\n      },\n      borders: {\n        top: { style: 'SOLID', width: 1 },\n        bottom: { style: 'SOLID', width: 1 },\n        left: { style: 'SOLID', width: 1 },\n        right: { style: 'SOLID', width: 1 }\n      },\n      verticalAlignment: 'TOP'\n    },\n    'CATEGORY': {\n      textFormat: {\n        fontSize: 11,\n        foregroundColor: { red: 0.3, green: 0.3, blue: 0.3 }\n      },\n      backgroundColor: { red: 0.95, green: 0.98, blue: 0.95 }\n    }\n  };\n  return formats[style] || formats['TABLE_CELL'];\n}\n\n// Parsing des donn\u00e9es\nlet parsedData;\ntry {\n  parsedData = parseAgentOutput(agentOutput);\n} catch (error) {\n  throw new Error(`Erreur lors du parsing des donn\u00e9es: ${error.message}`);\n}\n\n// Construction des requ\u00eates et donn\u00e9es\nconst requests = [];\nconst valueRanges = [];\n\ntry {\n  // D'abord, effacer le contenu de la feuille par d\u00e9faut\n  requests.push({\n    updateCells: {\n      range: {\n        sheetId: 0\n      },\n      fields: '*'\n    }\n  });\n\n  // 1. Mise \u00e0 jour de la feuille par d\u00e9faut (ID 0) -> \"R\u00e9sum\u00e9\"\n  requests.push(createOrUpdateSheet('Resume', 0, {\n    tabColor: { red: 0.2, green: 0.5, blue: 0.8 }\n  }));\n\n  // Donn\u00e9es de la feuille R\u00e9sum\u00e9\n  const summaryData = [\n    [parsedData.title || 'Donn\u00e9es extraites'],\n    [''],\n    ['R\u00e9sum\u00e9 des donn\u00e9es:'],\n    [`Nombre de cat\u00e9gories: ${parsedData.categories.length}`],\n    [`Nombre d'emplois: ${parsedData.jobs.length}`],\n    [''],\n    ['Cat\u00e9gories d\\'emploi:']\n  ];\n\n  parsedData.categories.forEach(category => {\n    summaryData.push([`\u2022 ${category}`]);\n  });\n\n  valueRanges.push(addSheetData('Resume', 'A1', summaryData));\n\n  // Formatage de la feuille R\u00e9sum\u00e9\n  requests.push(formatCells(0, { startRow: 0, endRow: 1, startCol: 0, endCol: 3 }, getTextFormat('TITLE')));\n  requests.push(formatCells(0, { startRow: 2, endRow: 3, startCol: 0, endCol: 3 }, getTextFormat('SECTION_HEADER')));\n  requests.push(formatCells(0, { startRow: 6, endRow: 7, startCol: 0, endCol: 3 }, getTextFormat('SECTION_HEADER')));\n  \n  const categoryStartRow = 7;\n  const categoryEndRow = categoryStartRow + parsedData.categories.length;\n  requests.push(formatCells(0, { \n    startRow: categoryStartRow, \n    endRow: categoryEndRow, \n    startCol: 0, \n    endCol: 2 \n  }, getTextFormat('CATEGORY')));\n\n  // 2. Cr\u00e9ation de la feuille \"Cat\u00e9gories\"\n  requests.push(createOrUpdateSheet('Categories', 1, {\n    tabColor: { red: 0.8, green: 0.9, blue: 0.7 }\n  }));\n\n  const categoriesData = [\n    ['Cat\u00e9gories d\\'emploi'],\n    [''],\n    ['Cat\u00e9gorie', 'Num\u00e9ro']\n  ];\n\n  parsedData.categories.forEach((category, index) => {\n    categoriesData.push([category, index + 1]);\n  });\n\n  valueRanges.push(addSheetData('Categories', 'A1', categoriesData));\n\n  requests.push(formatCells(1, { startRow: 0, endRow: 1, startCol: 0, endCol: 2 }, getTextFormat('TITLE')));\n  requests.push(formatCells(1, { startRow: 2, endRow: 3, startCol: 0, endCol: 2 }, getTextFormat('TABLE_HEADER')));\n  \n  if (parsedData.categories.length > 0) {\n    requests.push(formatCells(1, { \n      startRow: 3, \n      endRow: 3 + parsedData.categories.length, \n      startCol: 0, \n      endCol: 2 \n    }, getTextFormat('TABLE_CELL')));\n  }\n\n  // 3. Cr\u00e9ation de la feuille \"Emplois\"\n  requests.push(createOrUpdateSheet('Emplois', 2, {\n    tabColor: { red: 0.9, green: 0.7, blue: 0.8 }\n  }));\n\n  if (parsedData.jobs.length > 0) {\n    const jobHeaders = Object.keys(parsedData.jobs[0]);\n    const jobsData = [\n      ['Liste des emplois disponibles'],\n      [''],\n      jobHeaders\n    ];\n\n    parsedData.jobs.forEach(job => {\n      const jobRow = jobHeaders.map(header => job[header] || '');\n      jobsData.push(jobRow);\n    });\n\n    valueRanges.push(addSheetData('Emplois', 'A1', jobsData));\n\n    requests.push(formatCells(2, { startRow: 0, endRow: 1, startCol: 0, endCol: jobHeaders.length }, getTextFormat('TITLE')));\n    requests.push(formatCells(2, { startRow: 2, endRow: 3, startCol: 0, endCol: jobHeaders.length }, getTextFormat('TABLE_HEADER')));\n    \n    if (parsedData.jobs.length > 0) {\n      requests.push(formatCells(2, { \n        startRow: 3, \n        endRow: 3 + parsedData.jobs.length, \n        startCol: 0, \n        endCol: jobHeaders.length \n      }, getTextFormat('TABLE_CELL')));\n    }\n  }\n\n  // 4. Cr\u00e9ation de la feuille \"Statistiques\"\n  requests.push(createOrUpdateSheet('Statistiques', 3, {\n    tabColor: { red: 0.95, green: 0.8, blue: 0.6 }\n  }));\n\n  const companyCount = [...new Set(parsedData.jobs.map(job => job.Entreprise || job.Company || ''))].length;\n  const roleTypes = {};\n  \n  parsedData.jobs.forEach(job => {\n    const role = job.R\u00f4le || job.Role || '';\n    const roleType = role.split(' ')[0];\n    roleTypes[roleType] = (roleTypes[roleType] || 0) + 1;\n  });\n\n  const statsData = [\n    ['Statistiques des donn\u00e9es'],\n    [''],\n    ['M\u00e9trique', 'Valeur'],\n    ['Nombre total d\\'emplois', parsedData.jobs.length],\n    ['Nombre d\\'entreprises uniques', companyCount],\n    ['Nombre de cat\u00e9gories', parsedData.categories.length],\n    [''],\n    ['Types de r\u00f4les les plus fr\u00e9quents:'],\n    ['Type de r\u00f4le', 'Occurrences']\n  ];\n\n  const sortedRoleTypes = Object.entries(roleTypes)\n    .sort(([,a], [,b]) => b - a)\n    .slice(0, 10);\n\n  sortedRoleTypes.forEach(([role, count]) => {\n    statsData.push([role, count]);\n  });\n\n  valueRanges.push(addSheetData('Statistiques', 'A1', statsData));\n\n  requests.push(formatCells(3, { startRow: 0, endRow: 1, startCol: 0, endCol: 2 }, getTextFormat('TITLE')));\n  requests.push(formatCells(3, { startRow: 2, endRow: 3, startCol: 0, endCol: 2 }, getTextFormat('TABLE_HEADER')));\n  requests.push(formatCells(3, { startRow: 7, endRow: 8, startCol: 0, endCol: 2 }, getTextFormat('SECTION_HEADER')));\n  requests.push(formatCells(3, { startRow: 8, endRow: 9, startCol: 0, endCol: 2 }, getTextFormat('TABLE_HEADER')));\n\n  // Auto-redimensionnement des colonnes\n  for (let i = 0; i <= 3; i++) {\n    requests.push({\n      autoResizeDimensions: {\n        dimensions: {\n          sheetId: i,\n          dimension: 'COLUMNS',\n          startIndex: 0,\n          endIndex: 10\n        }\n      }\n    });\n  }\n\n} catch (error) {\n  console.error('Erreur lors de la g\u00e9n\u00e9ration du contenu:', error);\n  throw new Error(`Erreur lors de la g\u00e9n\u00e9ration du contenu: ${error.message}`);\n}\n\n// Validation finale des donn\u00e9es\nif (!requests || requests.length === 0) {\n  throw new Error('Aucune requ\u00eate g\u00e9n\u00e9r\u00e9e - v\u00e9rifiez les donn\u00e9es d\\'entr\u00e9e');\n}\n\nif (!valueRanges || valueRanges.length === 0) {\n  throw new Error('Aucune donn\u00e9e g\u00e9n\u00e9r\u00e9e - v\u00e9rifiez les donn\u00e9es d\\'entr\u00e9e');\n}\n\n// Retour des donn\u00e9es pour l'API Google Sheets\nreturn {\n  json: {\n    spreadsheetId: spreadsheetId,\n    requests: requests,\n    valueRanges: valueRanges,\n    totalRequests: requests.length,\n    totalValueRanges: valueRanges.length,\n    sheetsCreated: 4,\n    dataProcessed: {\n      title: parsedData.title,\n      categoriesCount: parsedData.categories.length,\n      jobsCount: parsedData.jobs.length,\n      companiesCount: [...new Set(parsedData.jobs.map(job => job.Entreprise || job.Company || ''))].length\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "eccede4d-c790-4b60-a3cd-5e9f29ac8f0e",
      "name": "Populate Research Report",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1072,
        0
      ],
      "parameters": {
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.spreadsheetId }}:batchUpdate",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"requests\": {{ JSON.stringify($json.requests) }}\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSheetsOAuth2Api"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        },
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c9603254-752b-4f86-8d77-e6231699094b",
      "name": "Finalize Output Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1488,
        0
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "{\n  \"error_message\": \"{{ $json.error.message }}\",\n  \"error_code\": \"{{ $json.error.code }}\",\n  \"spreadsheet_id\": \"{{ $('Code').item.json.spreadsheetId }}\",\n  \"timestamp\": \"{{ new Date().toISOString() }}\"\n}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "0c07aba4-88c4-4ddd-93e8-26057f6104f8",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        -96
      ],
      "parameters": {
        "color": 4,
        "width": 400,
        "height": 256,
        "content": "## Entry point: \n### - The user sends a request in natural language."
      },
      "typeVersion": 1
    },
    {
      "id": "4a113616-2e51-4bb7-8995-1c657f12fa9b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -320
      ],
      "parameters": {
        "color": 4,
        "width": 256,
        "height": 128,
        "content": "## \u2699\ufe0f PREREQUISITES\n- **Google Gemini API Key**\n- **MCP API keys (Firecrawl, Brave, Apify)**\n- **Google Sheets credentials**\n- **Self-hosted n8n instance**"
      },
      "typeVersion": 1
    },
    {
      "id": "fadd1418-2831-4856-9913-de61ec11d418",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        -320
      ],
      "parameters": {
        "color": 4,
        "height": 128,
        "content": "## \ud83d\udcca DATA FLOW\n1. **Request \u2192 AI analysis**\n2. **Tool selection \u2192 Collection**\n3. **Aggregation \u2192 Structuring**  \n4. **Enrichment \u2192 Delivery**"
      },
      "typeVersion": 1
    },
    {
      "id": "6afc44f5-db99-46a8-96ca-5e788e48bfff",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        -320
      ],
      "parameters": {
        "color": 4,
        "width": 272,
        "height": 112,
        "content": "## \u26a0\ufe0f ERROR HANDLING\n- **MCP timeout: Automatic retry**\n- **API limit: Fallback modes**\n- **Missing data: Validation**"
      },
      "typeVersion": 1
    },
    {
      "id": "104c3b3a-ae84-4ef5-99fb-b84cf599975a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -112
      ],
      "parameters": {
        "color": 5,
        "width": 544,
        "height": 384,
        "content": "## Orchestration\n### - When chat message received\n### - Gemini Research Orchestrator\n### - Simple Memory"
      },
      "typeVersion": 1
    },
    {
      "id": "4bdd58e3-eddd-4fa3-82f4-bc84ec5498fc",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        288
      ],
      "parameters": {
        "width": 752,
        "height": 304,
        "content": "## Data Collection Tools \n### *The agent automatically selects the* \n### *appropriate tools based on the request.*\n### - Firecrawl (list + execute)\n### - Brave (list + execute)\n### - Apify (list + execute)\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "509c0f30-e634-46d5-83d2-b4aafd8090b8",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        -160
      ],
      "parameters": {
        "color": 3,
        "width": 544,
        "height": 304,
        "content": "## Report Generation\n### *The collected data is automatically organized and formatted.*\n### - Create Research Report\n### - Format Research Data\n### - Populate Research Report"
      },
      "typeVersion": 1
    },
    {
      "id": "7dd445d7-2785-4f0a-a938-e9e9e962e929",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1264,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 304,
        "content": "## Post-Processing\n### *Data enrichment and finalization for delivery*\n### - Data Enrichment Request\n### - Finalize Output Data"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "94fda3e6-3671-42e8-9f8a-d9d7688722eb",
  "connections": {
    "Apify list": {
      "ai_tool": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Brave list": {
      "ai_tool": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Apify execute": {
      "ai_tool": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Brave execute": {
      "ai_tool": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Firecrawl list": {
      "ai_tool": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Firecrawl execute": {
      "ai_tool": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Finalize Output Data": {
      "main": [
        []
      ]
    },
    "Format Research Data": {
      "main": [
        [
          {
            "node": "Populate Research Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Research Report": {
      "main": [
        [
          {
            "node": "Format Research Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Enrichment Request": {
      "main": [
        [
          {
            "node": "Finalize Output Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Populate Research Report": {
      "main": [
        [
          {
            "node": "Data Enrichment Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When chat message received": {
      "main": [
        [
          {
            "node": "Gemini Research Orchestrator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Research Orchestrator": {
      "main": [
        [
          {
            "node": "Create Research Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

⚠️ IMPORTANT: This template requires self-hosted n8n hosting due to the use of community nodes (MCP tools). It will not work on n8n Cloud. Make sure you have access to a self-hosted n8n instance before using this template.

Source: https://n8n.io/workflows/8218/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

teste. Uses chatTrigger, agent, lmChatGroq, memoryBufferWindow. Chat trigger; 24 nodes.

Chat Trigger, Agent, Groq Chat +7
AI & RAG

pix-zap. Uses chatTrigger, agent, toolCalculator, toolWikipedia. Chat trigger; 21 nodes.

Chat Trigger, Agent, Tool Calculator +7
AI & RAG

Who is this workflow for? This workflow is designed for SEO analysts, content creators, marketing agencies, and developers who need to index a website and then interact with its content as if it were

Agent, OpenAI Chat, Memory Buffer Window +10
AI & RAG

This Chatbot automates the process of discovering job openings and generating tailored job application emails.

Chat Trigger, OpenAI Chat, Mcp Client Tool +12
AI & RAG

This project is an automation workflow that generates a personalized resume and cover letter for each job listing. Generates an HTML resume from your data. Hosts it live on GitHub Pages. Converts it t

HTTP Request, Agent, OpenAI Chat +10