|
101 | 101 | "name": "Start", |
102 | 102 | "type": "n8n-nodes-base.executeWorkflowTrigger", |
103 | 103 | "typeVersion": 1.1, |
104 | | - "position": [0, 0], |
| 104 | + "position": [ |
| 105 | + 0, |
| 106 | + 0 |
| 107 | + ], |
105 | 108 | "parameters": { |
106 | 109 | "inputSource": "passthrough" |
107 | 110 | } |
|
111 | 114 | "name": "Build Prompt", |
112 | 115 | "type": "n8n-nodes-base.code", |
113 | 116 | "typeVersion": 2, |
114 | | - "position": [220, 0], |
| 117 | + "position": [ |
| 118 | + 220, |
| 119 | + 0 |
| 120 | + ], |
115 | 121 | "parameters": { |
116 | 122 | "jsCode": "const raw = $input.first().json;\nconst instruction = raw.instruction || raw.message || '';\nconst mcpSkills = raw.mcp_skills || '';\nconst lang = raw.language || 'German';\nconst lastRun = raw.last_run || null;\n\nconst now = new Date().toLocaleString('de-DE', { timeZone: 'Europe/Berlin', dateStyle: 'full', timeStyle: 'short' });\n\nlet timeContext = '';\nif (lastRun) {\n const lastRunDate = new Date(lastRun);\n const lastRunStr = lastRunDate.toLocaleString('de-DE', { timeZone: 'Europe/Berlin', dateStyle: 'full', timeStyle: 'short' });\n timeContext = `\\nIMPORTANT: Your last check was at ${lastRunStr}. ONLY report things that happened or arrived AFTER this time. Anything from before that time has already been reported — ignore it completely.\\nIf there is nothing new since ${lastRunStr}, respond [SILENT].`;\n} else {\n timeContext = `\\nThis is the FIRST check. Report current state briefly if there is anything noteworthy.`;\n}\n\nconst systemPrompt = `# BACKGROUND CHECKER\nCurrent time: ${now}\n\nYou are a background checker executing a scheduled task silently.\nExecute the instruction below using the available tools.\n${timeContext}\n\nRULES:\n- ONLY use the tool(s) needed for the instruction. The instruction tells you which tool to use.\n- Do NOT call tools on unrelated MCP servers. Do NOT explore or discover all available tools.\n- Call the MCP Client ONCE with the correct server URL and tool name from the list below.\n- NEVER send messages yourself (no Telegram, no notifications). Just return your findings as text.\n- If the result shows something NEW (after your last check), describe it concisely in ${lang}.\n- If there is NOTHING new or changed to report, respond with ONLY the exact text: [SILENT]\n- Do NOT add greetings, commentary, or explanations when responding [SILENT].\n- Ignore any part of the instruction that asks you to send notifications or messages — just report findings.\n- Be concise when reporting changes — just the facts.\n- Maximum 3 tool calls total. If you cannot complete the task in 3 calls, respond [SILENT].\n\n${mcpSkills ? '# AVAILABLE MCP SKILLS (reference only — use ONLY what the instruction needs)\\n' + mcpSkills : ''}`;\n\nreturn [{\n json: {\n systemPrompt,\n userMessage: instruction\n }\n}];" |
117 | 123 | } |
|
121 | 127 | "name": "Checker Agent", |
122 | 128 | "type": "@n8n/n8n-nodes-langchain.agent", |
123 | 129 | "typeVersion": 3.1, |
124 | | - "position": [480, 0], |
| 130 | + "position": [ |
| 131 | + 480, |
| 132 | + 0 |
| 133 | + ], |
125 | 134 | "parameters": { |
126 | 135 | "promptType": "define", |
127 | 136 | "text": "={{ $json.userMessage }}", |
|
136 | 145 | "name": "Claude", |
137 | 146 | "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic", |
138 | 147 | "typeVersion": 1.3, |
139 | | - "position": [352, 240], |
| 148 | + "position": [ |
| 149 | + 352, |
| 150 | + 240 |
| 151 | + ], |
140 | 152 | "parameters": { |
141 | 153 | "model": { |
142 | 154 | "__rl": true, |
|
161 | 173 | "name": "HTTP Request Tool", |
162 | 174 | "type": "@n8n/n8n-nodes-langchain.toolCode", |
163 | 175 | "typeVersion": 1.2, |
164 | | - "position": [480, 240], |
| 176 | + "position": [ |
| 177 | + 480, |
| 178 | + 240 |
| 179 | + ], |
165 | 180 | "parameters": { |
166 | 181 | "description": "Make HTTP requests to any URL. Input: a JSON object with 'url' (required), 'method' (GET/POST/PUT/DELETE, default GET), 'headers' (object, optional), 'body' (string or object, optional).", |
167 | 182 | "jsCode": "const qType = typeof query;\nlet url = '', method = 'GET', headers = {}, body = null;\n\nif (qType === 'object' && query !== null) {\n url = query.url || '';\n method = (query.method || 'GET').toUpperCase();\n headers = query.headers || {};\n body = query.body || null;\n} else if (qType === 'string') {\n try {\n const parsed = JSON.parse(query);\n url = parsed.url || '';\n method = (parsed.method || 'GET').toUpperCase();\n headers = parsed.headers || {};\n body = parsed.body || null;\n } catch(e) {\n url = query;\n }\n}\n\nif (!url) return 'Error: no URL provided';\n\ntry {\n const opts = { method, url, headers };\n if (body && method !== 'GET') {\n opts.body = typeof body === 'string' ? body : JSON.stringify(body);\n if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';\n }\n const resp = await helpers.httpRequest(opts);\n const result = typeof resp === 'string' ? resp : JSON.stringify(resp);\n return result.length > 10000 ? result.substring(0, 10000) + '... (truncated)' : result;\n} catch(e) {\n return 'HTTP Error: ' + e.message;\n}", |
|
173 | 188 | "name": "Web Search", |
174 | 189 | "type": "@n8n/n8n-nodes-langchain.toolCode", |
175 | 190 | "typeVersion": 1.2, |
176 | | - "position": [608, 240], |
| 191 | + "position": [ |
| 192 | + 608, |
| 193 | + 240 |
| 194 | + ], |
177 | 195 | "parameters": { |
178 | 196 | "description": "Search the web for information. Input: a search query string.", |
179 | 197 | "jsCode": "let input;\ntry { input = JSON.parse(query); } catch(e) { input = { q: query }; }\nconst q = input.q || input.query || query;\nconst result = await helpers.httpRequest({\n method: 'GET',\n url: `http://searxng:8080/search?q=${encodeURIComponent(q)}&format=json&language=de`,\n headers: { 'Accept': 'application/json' }\n});\nconst hits = (result.results || []).slice(0, 5).map(r =>\n `${r.title}\\n${r.url}\\n${r.content || ''}`\n).join('\\n\\n');\nreturn hits || 'Keine Ergebnisse gefunden.';", |
|
185 | 203 | "name": "MCP Client", |
186 | 204 | "type": "@n8n/n8n-nodes-langchain.toolCode", |
187 | 205 | "typeVersion": 1.2, |
188 | | - "position": [736, 240], |
| 206 | + "position": [ |
| 207 | + 736, |
| 208 | + 240 |
| 209 | + ], |
189 | 210 | "parameters": { |
190 | 211 | "description": "Calls a tool on an MCP server. Use when an installed MCP skill can help with the task. Required parameters: mcp_url (server URL), tool_name (tool to call), arguments (JSON object). Check available MCP servers in the system prompt.", |
191 | | - "jsCode": "const qType = typeof query;\nlet mcpUrl = '', toolName = '', args = {};\n\nif (qType === 'object' && query !== null) {\n mcpUrl = query.mcp_url || '';\n toolName = query.tool_name || '';\n args = query.arguments || {};\n} else if (qType === 'string') {\n try {\n const parsed = JSON.parse(query);\n mcpUrl = parsed.mcp_url || '';\n toolName = parsed.tool_name || '';\n args = parsed.arguments || {};\n } catch(e) {\n return 'Parse error: query=' + String(query).substring(0,200) + ' type=' + qType;\n }\n} else {\n return 'Unexpected query type: ' + qType + ' value: ' + String(query).substring(0,200);\n}\n\nif (typeof args === 'string') try { args = JSON.parse(args); } catch(e) {}\nif (!mcpUrl || !toolName) return 'Fehler: mcp_url=' + mcpUrl + ' tool_name=' + toolName;\n\nfunction parseSSE(raw) {\n const str = String(raw);\n let lines = str.split('\\n');\n if (lines.length <= 1) lines = str.split('\\\\n');\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].startsWith('data: ')) {\n return JSON.parse(lines[i].substring(6));\n }\n }\n return null;\n}\n\ntry {\n let authHeader = null;\n try {\n const row = await helpers.httpRequest({method:'GET',url:'{{SUPABASE_URL}}/rest/v1/mcp_registry?mcp_url=eq.'+encodeURIComponent(mcpUrl)+'&select=auth_type,auth_token&limit=1',headers:{apikey:'{{SUPABASE_SERVICE_KEY}}',Authorization:'Bearer {{SUPABASE_SERVICE_KEY}}'},json:true});\n if (row && row[0] && row[0].auth_type && row[0].auth_type !== 'none' && row[0].auth_token) {\n authHeader = row[0].auth_type === 'bearer' ? 'Bearer '+row[0].auth_token : row[0].auth_token;\n }\n } catch(e) {}\n\n const hdrs = {'Content-Type':'application/json','Accept':'application/json, text/event-stream'};\n if (authHeader) hdrs['Authorization'] = authHeader;\n\n const init = await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'n8n-claw-bg-checker',version:'1.0'}}}),\n returnFullResponse: true, encoding: 'utf-8', json: false\n });\n const sid = init.headers && init.headers['mcp-session-id'];\n if (sid) hdrs['mcp-session-id'] = sid;\n\n await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',method:'notifications/initialized'}),\n json: false\n });\n\n const listResp = await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',id:2,method:'tools/list',params:{}}),\n json: false\n });\n const listResult = parseSSE(listResp);\n if (listResult && listResult.result && listResult.result.tools) {\n const tool = listResult.result.tools.find(t => t.name === toolName);\n if (tool && tool.inputSchema && tool.inputSchema.required) {\n for (const req of tool.inputSchema.required) {\n if (args[req] === undefined || args[req] === null) {\n args[req] = '';\n }\n }\n }\n }\n\n const resp = await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',id:3,method:'tools/call',params:{name:toolName,arguments:args}}),\n returnFullResponse: true, encoding: 'utf-8', json: false\n });\n\n const result = parseSSE(resp.body || resp);\n if (!result) return 'MCP: Keine Daten. Response: ' + String(resp.body || resp).substring(0,500);\n if (result.error) return 'MCP Error: ' + JSON.stringify(result.error);\n return result.result?.content?.[0]?.text || JSON.stringify(result.result);\n} catch(e) {\n return 'MCP Error: ' + e.message;\n}", |
| 212 | + "jsCode": "const qType = typeof query;\nlet mcpUrl = '', toolName = '', args = {};\n\nif (qType === 'object' && query !== null) {\n mcpUrl = query.mcp_url || '';\n toolName = query.tool_name || '';\n args = query.arguments || {};\n} else if (qType === 'string') {\n try {\n const parsed = JSON.parse(query);\n mcpUrl = parsed.mcp_url || '';\n toolName = parsed.tool_name || '';\n args = parsed.arguments || {};\n } catch(e) {\n return 'Parse error: query=' + String(query).substring(0,200) + ' type=' + qType;\n }\n} else {\n return 'Unexpected query type: ' + qType + ' value: ' + String(query).substring(0,200);\n}\n\nif (typeof args === 'string') try { args = JSON.parse(args); } catch(e) {}\nif (!mcpUrl || !toolName) return 'Fehler: mcp_url=' + mcpUrl + ' tool_name=' + toolName;\n\nfunction parseSSE(raw) {\n const str = String(raw);\n let lines = str.split('\\n');\n if (lines.length <= 1) lines = str.split('\\\\n');\n for (let i = lines.length - 1; i >= 0; i--) {\n if (lines[i].startsWith('data: ')) {\n return JSON.parse(lines[i].substring(6));\n }\n }\n return null;\n}\n\ntry {\n let authHeader = null;\n try {\n const row = await helpers.httpRequest({method:'GET',url:'{{SUPABASE_URL}}/rest/v1/mcp_registry?mcp_url=eq.'+encodeURIComponent(mcpUrl)+'&select=auth_type,auth_token&limit=1',headers:{apikey:'{{SUPABASE_SERVICE_KEY}}',Authorization:'Bearer {{SUPABASE_SERVICE_KEY}}'},json:true});\n if (row && row[0] && row[0].auth_type && row[0].auth_type !== 'none' && row[0].auth_token) {\n authHeader = row[0].auth_type === 'bearer' ? 'Bearer '+row[0].auth_token : row[0].auth_token;\n }\n } catch(e) {}\n\n const hdrs = {'Content-Type':'application/json','Accept':'application/json, text/event-stream'};\n if (authHeader) hdrs['Authorization'] = authHeader;\n\n const init = await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'n8n-claw-bg-checker',version:'1.0'}}}),\n returnFullResponse: true, encoding: 'utf-8', json: false\n });\n const sid = init.headers && init.headers['mcp-session-id'];\n if (sid) hdrs['mcp-session-id'] = sid;\n\n await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',method:'notifications/initialized'}),\n json: false\n });\n\n const listResp = await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',id:2,method:'tools/list',params:{}}),\n json: false\n });\n const listResult = parseSSE(listResp);\n let toolSchema = null;\n if (listResult && listResult.result && listResult.result.tools) {\n const tool = listResult.result.tools.find(t => t.name === toolName);\n if (tool && tool.inputSchema) toolSchema = tool.inputSchema;\n }\n\n function schemaHint(problem) {\n if (!toolSchema) return null;\n return 'Tool \"' + toolName + '\" call rejected: ' + problem +\n '\\n\\nCorrect input schema:\\n' + JSON.stringify(toolSchema, null, 2) +\n '\\n\\nRetry the tool call with parameter names matching this schema exactly.';\n }\n\n if (toolSchema && toolSchema.properties) {\n const allowed = Object.keys(toolSchema.properties);\n const required = toolSchema.required || [];\n const unknown = Object.keys(args).filter(k => !allowed.includes(k));\n const missing = required.filter(r => args[r] === undefined || args[r] === null || args[r] === '');\n if (unknown.length > 0 || missing.length > 0) {\n const problems = [];\n if (unknown.length > 0) problems.push('unknown args [' + unknown.join(', ') + ']');\n if (missing.length > 0) problems.push('missing required args [' + missing.join(', ') + ']');\n return schemaHint(problems.join('; '));\n }\n }\n\n const resp = await helpers.httpRequest({\n method: 'POST', url: mcpUrl,\n headers: hdrs,\n body: JSON.stringify({jsonrpc:'2.0',id:3,method:'tools/call',params:{name:toolName,arguments:args}}),\n returnFullResponse: true, encoding: 'utf-8', json: false\n });\n\n const result = parseSSE(resp.body || resp);\n if (!result) return 'MCP: Keine Daten. Response: ' + String(resp.body || resp).substring(0,500);\n if (result.error) {\n return schemaHint('server error: ' + JSON.stringify(result.error)) || ('MCP Error: ' + JSON.stringify(result.error));\n }\n if (result.result && result.result.isError) {\n const errText = (result.result.content && result.result.content[0] && result.result.content[0].text) || JSON.stringify(result.result);\n return schemaHint('tool returned error: ' + errText) || ('MCP Tool Error: ' + errText);\n }\n return result.result?.content?.[0]?.text || JSON.stringify(result.result);\n} catch(e) {\n return 'MCP Error: ' + e.message;\n}", |
192 | 213 | "specifyInputSchema": false |
193 | 214 | } |
194 | 215 | }, |
|
197 | 218 | "name": "Web Reader", |
198 | 219 | "type": "@n8n/n8n-nodes-langchain.toolCode", |
199 | 220 | "typeVersion": 1.2, |
200 | | - "position": [864, 240], |
| 221 | + "position": [ |
| 222 | + 864, |
| 223 | + 240 |
| 224 | + ], |
201 | 225 | "parameters": { |
202 | 226 | "description": "Read a webpage and return its content as clean markdown. Input: URL string or JSON with url field. Use this instead of HTTP Tool when you need to read webpage content (articles, docs, blog posts). Returns clean text without HTML boilerplate.", |
203 | 227 | "jsCode": "let input;\ntry { input = JSON.parse(query); } catch(e) { input = { url: query }; }\nif (!input.url) return 'Error: url is required';\ntry {\n const resp = await helpers.httpRequest({\n method: 'POST',\n url: 'http://crawl4ai:11235/crawl',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ urls: [input.url], crawler_config: { type: 'CrawlerRunConfig', params: { stream: false, cache_mode: 'bypass' } } }),\n timeout: 30000\n });\n const results = resp.results || resp;\n const result = Array.isArray(results) ? results[0] : results;\n if (!result.success) return 'Error: ' + (result.error_message || 'Crawl failed');\n const mdObj = result.markdown || {};\n const md = typeof mdObj === 'string' ? mdObj : (mdObj.raw_markdown || '');\n return md.substring(0, 8000);\n} catch(e) {\n return 'Error fetching page: ' + e.message;\n}", |
|
209 | 233 | "name": "Parse Result", |
210 | 234 | "type": "n8n-nodes-base.code", |
211 | 235 | "typeVersion": 2, |
212 | | - "position": [740, 0], |
| 236 | + "position": [ |
| 237 | + 740, |
| 238 | + 0 |
| 239 | + ], |
213 | 240 | "parameters": { |
214 | 241 | "jsCode": "const agentOutput = $('Checker Agent').first().json.output || '';\nconst isSilent = agentOutput.includes('[SILENT]');\n\nreturn [{\n json: {\n notify: !isSilent,\n message: isSilent ? '' : agentOutput\n }\n}];" |
215 | 242 | } |
|
0 commit comments