This workflow follows the Chainllm โ Execute Workflow 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 โ
{
"settings": {
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"name": "\ud83c\udfd7\ufe0f MCP Builder",
"connections": {
"Start": {
"main": [
[
{
"node": "Search API Docs",
"type": "main",
"index": 0
}
]
]
},
"BuildPrompt": {
"main": [
[
{
"node": "Generate Tool",
"type": "main",
"index": 0
}
]
]
},
"Generate Tool": {
"main": [
[
{
"node": "Assemble & Deploy",
"type": "main",
"index": 0
}
]
]
},
"Assemble & Deploy": {
"main": [
[
{
"node": "Create Sub-Workflow",
"type": "main",
"index": 0
}
]
]
},
"Anthropic Chat Model": {
"ai_languageModel": [
[
{
"node": "Generate Tool",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Result": {
"main": [
[
{
"node": "Register MCP",
"type": "main",
"index": 0
}
]
]
},
"Register MCP": {
"main": [
[
{
"node": "Fetch Registry",
"type": "main",
"index": 0
}
]
]
},
"Fetch Registry": {
"main": [
[
{
"node": "Build Instructions",
"type": "main",
"index": 0
}
]
]
},
"Build Instructions": {
"main": [
[
{
"node": "Update Agent Config",
"type": "main",
"index": 0
}
]
]
},
"Update Agent Config": {
"main": [
[
{
"node": "Finalize Response",
"type": "main",
"index": 0
}
]
]
},
"Search API Docs": {
"main": [
[
{
"node": "Pick Best Result",
"type": "main",
"index": 0
}
]
]
},
"Pick Best Result": {
"main": [
[
{
"node": "Fetch Doc Content",
"type": "main",
"index": 0
}
]
]
},
"Fetch Doc Content": {
"main": [
[
{
"node": "BuildPrompt",
"type": "main",
"index": 0
}
]
]
},
"Test MCP": {
"main": [
[
{
"node": "Test Passed?",
"type": "main",
"index": 0
}
]
]
},
"Test Passed?": {
"main": [
[
{
"node": "Result",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Fix Prompt",
"type": "main",
"index": 0
}
]
]
},
"Build Fix Prompt": {
"main": [
[
{
"node": "Fix LLM",
"type": "main",
"index": 0
}
]
]
},
"Fix LLM": {
"main": [
[
{
"node": "Patch & Retest",
"type": "main",
"index": 0
}
]
]
},
"Patch & Retest": {
"main": [
[
{
"node": "Result",
"type": "main",
"index": 0
}
]
]
},
"Fix Model": {
"ai_languageModel": [
[
{
"node": "Fix LLM",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Patch Tool Schema": {
"main": [
[
{
"node": "Build MCP JSON",
"type": "main",
"index": 0
}
]
]
},
"Create Sub-Workflow": {
"main": [
[
{
"node": "Patch Tool Schema",
"type": "main",
"index": 0
}
]
]
},
"Create MCP Workflow": {
"main": [
[
{
"node": "Test MCP",
"type": "main",
"index": 0
}
]
]
},
"Build MCP JSON": {
"main": [
[
{
"node": "Create MCP Workflow",
"type": "main",
"index": 0
}
]
]
}
},
"nodes": [
{
"parameters": {
"url": "=http://searxng:8080/search?q={{ encodeURIComponent((JSON.parse($json.query || '{}').task || $json.query || '') + ' API documentation REST reference') }}&format=json&language=en&engines=google,duckduckgo,brave",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
}
]
},
"options": {}
},
"id": "search-api-docs",
"name": "Search API Docs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
224,
-304
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const raw = $('Start').first().json;\nlet task = '';\ntry {\n const p = JSON.parse(raw.query || '{}');\n task = p.task || p.description || p.query || raw.query || '';\n} catch(e) {\n task = raw.task || raw.query || String(raw);\n}\n\nconst stopWords = new Set(['build','baue','bau','erstelle','create','make','einen','eine','ein','der','die','das','und','or','the','a','an','with','mit','von','from','for','fuer','that','which','tool','mcp','server','named','namens','called','does','following','api','parameter','return','returns','using','http','get','post','json','format','data','request','requests','response','should','soll','dem','den','des','als','nimmt','ruft','auf','gibt','zurueck','titel','autor','isbn','buches','strukturiertes','antwort']);\nconst keywords = task.toLowerCase().replace(/[^a-z0-9\\s]/g, ' ').split(/\\s+/).filter(w => w.length > 2 && !stopWords.has(w));\n\n// Extract URLs mentioned in task to identify the target API domain\nconst urlMatches = task.match(/https?:\\/\\/([^\\s\\/\\)]+)/g) || [];\nconst taskDomains = urlMatches.map(u => { try { return u.replace(/^https?:\\/\\//, '').split('/')[0].replace('www.',''); } catch(e) { return ''; } }).filter(Boolean);\n\nconst results = $json.results || [];\nif (results.length === 0) return [{ json: { bestUrl: '', results, task } }];\n\nconst scored = results.map((r, i) => {\n const url = (r.url || '').toLowerCase();\n const title = (r.title || '').toLowerCase();\n const text = title + ' ' + url;\n let score = 0;\n\n // Keyword matches in title+url\n for (const kw of keywords) { if (text.includes(kw)) score += 2; }\n\n // Strong bonus: URL domain matches a domain mentioned in the task\n for (const d of taskDomains) { if (url.includes(d)) score += 10; }\n\n // Bonus for official API doc URL patterns\n if (/\\/api\\/|\\/docs\\/|\\/dev\\/|\\/developer|\\/reference|\\/documentation/.test(url)) score += 5;\n\n // Penalty for generic sites\n if (/stackoverflow\\.com|reddit\\.com|learn\\.microsoft\\.com|medium\\.com|w3tutorials|arcanecode|baeldung/.test(url)) score -= 5;\n\n // Slight position bonus\n score -= i * 0.1;\n return { url: r.url, title: r.title, score, index: i };\n});\n\nscored.sort((a, b) => b.score - a.score);\nconst best = scored[0];\nreturn [{ json: { bestUrl: best.url || results[0]?.url || '', results, task, pickedIndex: best.index, pickedTitle: best.title, pickedScore: best.score } }];"
},
"id": "pick-best-result",
"name": "Pick Best Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
336,
-304
]
},
{
"parameters": {
"url": "={{ 'https://r.jina.ai/' + ($json.bestUrl || '') }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "text/plain"
},
{
"name": "X-Return-Format",
"value": "text"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "text"
}
},
"timeout": 15000
}
},
"id": "fetch-doc-content",
"name": "Fetch Doc Content",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
560,
-304
],
"continueOnFail": true
},
{
"parameters": {
"inputSource": "passthrough"
},
"id": "trigger",
"name": "Start",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
0,
-304
]
},
{
"parameters": {
"jsCode": "\nconst raw = $('Start').first().json;\nlet task = '';\ntry {\n const p = JSON.parse(raw.query || '{}');\n task = p.task || p.description || p.query || raw.query || '';\n} catch(e) {\n task = raw.task || raw.query || String(raw);\n}\ntask = task.replace(/^task:\\s*/i, '').trim();\n\nlet docsContext = '';\ntry {\n const searchResults = $('Pick Best Result').first().json;\n const topResults = (searchResults?.results || []).slice(0,3).map(r => `- ${r.title}: ${r.url}`).join('\\n');\n const docText = $('Fetch Doc Content').first().json?.body || $('Fetch Doc Content').first().json?.data || '';\n const truncated = String(docText).substring(0, 8000).trim();\n if (truncated && truncated.length > 100) {\n docsContext = `\\n\\nAPI DOKUMENTATION:\\nQuellen:\\n${topResults}\\n\\nInhalt:\\n${truncated}`;\n } else if (topResults) {\n docsContext = `\\n\\nGefundene Quellen:\\n${topResults}`;\n }\n} catch(e) { docsContext = ''; }\n\nconst words = task.toLowerCase().split(/\\s+/).filter(w => w.length > 2 && !['ein','eine','der','die','das','und','oder','via','mit','fuer','ueber','von','aus','baue','bau','erstelle','mcp','server','tool','einen','neue','neuen','neues'].includes(w));\nconst serverName = words.slice(0, 3).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ') || 'Custom Tool';\nconst path = words.slice(0, 2).join('-') || 'custom-tool';\n\nconst prompt = `Generiere JavaScript Code fuer einen n8n Sub-Workflow der API-Anfragen bearbeitet.\n\nAUFGABE: ${task}\n${docsContext}\n\nDer Code laeuft in einem n8n Code-Node. Parameter kommen ueber $json (z.B. $json.isbn).\n\nAntworte NUR mit einem JSON-Objekt:\n{\n \"tool_name\": \"snake_case_name\",\n \"description\": \"Was das Tool tut (1 Satz, fuer den AI Agent)\",\n \"params\": [{\"name\": \"param1\", \"description\": \"was dieser param bedeutet\", \"type\": \"string\"}],\n \"jsCode\": \"der JavaScript Code\"\n}\n\nREGELN fuer jsCode (laeuft in n8n Code Node, NICHT toolCode):\n- Parameter lesen: const isbn = $json.isbn || $input.first().json.isbn;\n- HTTP: await helpers.httpRequest({method:'GET', url:'...'})\n- Return MUSS ein Array sein: return [{json: {result: 'antwort als string'}}];\n- KEIN fetch(), KEIN require(), KEIN new URL()\n- Fehlerbehandlung einbauen\n\nBEISPIEL fuer ISBN lookup:\nconst isbn = $json.isbn || $input.first().json.isbn;\nif (!isbn) return [{json: {result: 'Fehler: ISBN fehlt'}}];\nconst data = await helpers.httpRequest({method:'GET', url:'https://openlibrary.org/api/books?bibkeys=ISBN:'+isbn+'&format=json&jscmd=data'});\nconst book = data['ISBN:'+isbn];\nif (!book) return [{json: {result: 'Nicht gefunden: '+isbn}}];\nreturn [{json: {result: 'Titel: '+book.title+', Autor: '+(book.authors?.[0]?.name||'unbekannt')}}];\n\nAntworte NUR mit dem JSON-Objekt!`;\n\nreturn [{json: {prompt, task, serverName, path}}];\n"
},
"id": "build-prompt",
"name": "BuildPrompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
672,
-304
]
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.prompt }}"
},
"id": "ai-chain",
"name": "Generate Tool",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.4,
"position": [
896,
-304
]
},
{
"parameters": {
"jsCode": "\nconst llmRaw = $input.first().json.text || $input.first().json.response || '';\nconst meta = $('BuildPrompt').first().json;\n\nlet toolDef;\ntry {\n const jsonMatch = llmRaw.match(/\\{[\\s\\S]*\\}/);\n toolDef = JSON.parse(jsonMatch[0]);\n} catch(e) {\n return [{json: {error: 'JSON parse failed', raw: llmRaw.substring(0,500)}}];\n}\n\nconst toolName = toolDef.tool_name || 'custom_tool';\nconst description = toolDef.description || meta.task;\nconst jsCode = toolDef.jsCode || '';\nconst params = toolDef.params || [];\n\n// sampleArgs for testing\nconst sampleObj = {};\nparams.forEach(p => { sampleObj[p.name || p] = 'test'; });\n\n// Sub-workflow JSON: Execute Workflow Trigger + Code node\nconst subWorkflow = {\n name: 'MCP Sub: ' + meta.serverName,\n settings: {callerPolicy: 'workflowsFromSameOwner'},\n nodes: [\n {\n id: 'sub-trigger',\n name: 'Execute Workflow Trigger',\n type: 'n8n-nodes-base.executeWorkflowTrigger',\n typeVersion: 1.1,\n position: [0, 0],\n parameters: {inputSource: 'passthrough'}\n },\n {\n id: 'sub-code',\n name: 'Run Tool',\n type: 'n8n-nodes-base.code',\n typeVersion: 2,\n position: [256, 0],\n parameters: {language: 'javaScript', jsCode: jsCode}\n }\n ],\n connections: {\n 'Execute Workflow Trigger': {main: [[{node: 'Run Tool', type: 'main', index: 0}]]}\n }\n};\n\n// MCP server workflow: mcpTrigger + toolWorkflow pointing to sub-workflow\n// (sub-workflow ID filled in after sub is created)\nconst workflowInputsSchema = params.map(p => ({\n id: p.name || p,\n displayName: p.name || p,\n type: (p.type || 'string'),\n description: p.description || '',\n required: true,\n defaultMatch: false,\n display: true,\n canBeUsedToMatch: true,\n removed: false\n}));\n\nconst workflowInputsValue = {};\nparams.forEach(p => {\n const pname = p.name || p;\n workflowInputsValue[pname] = `={{ $fromAI('${pname}', '${p.description || pname}', '${p.type || 'string'}') }}`;\n});\n\nreturn [{json: {\n subWorkflow: JSON.stringify(subWorkflow),\n toolName,\n description,\n path: meta.path,\n serverName: meta.serverName,\n sampleArgs: JSON.stringify(sampleObj),\n workflowInputsSchema: JSON.stringify(workflowInputsSchema),\n workflowInputsValue: JSON.stringify(workflowInputsValue),\n originalJsCode: jsCode\n}}];\n"
},
"id": "assemble",
"name": "Assemble & Deploy",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1248,
-304
]
},
{
"parameters": {
"method": "POST",
"url": "{{N8N_INTERNAL_URL}}/api/v1/workflows",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-N8N-API-KEY",
"value": "{{N8N_API_KEY}}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.subWorkflow }}",
"options": {}
},
"id": "create-wf",
"name": "Create Sub-Workflow",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1472,
-304
]
},
{
"parameters": {
"method": "POST",
"url": "{{N8N_INTERNAL_URL}}/api/v1/workflows",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-N8N-API-KEY",
"value": "{{N8N_API_KEY}}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.mcpWorkflowJson }}",
"options": {}
},
"id": "activate-wf",
"name": "Create MCP Workflow",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1696,
-304
]
},
{
"parameters": {
"model": {
"__rl": true,
"value": "claude-opus-4-6",
"mode": "list",
"cachedResultName": "Claude Opus 4.6"
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"typeVersion": 1.3,
"position": [
968,
-80
],
"id": "1a628b60-a6b8-46d9-9857-199bfb495b7a",
"name": "Anthropic Chat Model",
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "{{SUPABASE_URL}}/rest/v1/mcp_registry?on_conflict=path",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "apikey",
"value": "{{SUPABASE_SERVICE_KEY}}"
},
{
"name": "Authorization",
"value": "Bearer {{SUPABASE_SERVICE_KEY}}"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Prefer",
"value": "return=representation"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"server_name\": $('Build MCP JSON').first().json.serverName, \"path\": $('Build MCP JSON').first().json.path, \"mcp_url\": '{{N8N_URL}}/mcp/' + $('Build MCP JSON').first().json.path, \"description\": $('Assemble & Deploy').first().json.description, \"tools\": [ $('Build MCP JSON').first().json.toolName ], \"workflow_id\": $('Create MCP Workflow').first().json.id, \"active\": true } }}",
"options": {}
},
"id": "register-mcp",
"name": "Register MCP",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3392,
-304
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "\nconst summary = $('Result').first().json;\nconst supa = $('Register MCP').first()?.json;\nconst agentUpdate = $('Build Instructions').first()?.json;\n\nlet registryText;\nif (Array.isArray(supa) && supa.length) {\n registryText = `Registry: \u2705 ${supa[0].path}`;\n} else if (supa && supa.error) {\n registryText = 'Registry Fehler: ' + (supa.error.message || JSON.stringify(supa).substring(0,100));\n} else {\n registryText = 'Registry: gespeichert';\n}\n\nlet agentText;\nif (agentUpdate && agentUpdate.serverCount > 0) {\n agentText = `Agent Config: \u2705 aktualisiert (${agentUpdate.serverCount} MCP Server bekannt)`;\n} else {\n agentText = 'Agent Config: konnte nicht aktualisiert werden';\n}\n\nreturn [{ json: {\n response: summary.response + '\\n\\n' + registryText + '\\n' + agentText,\n workflow_id: summary.workflow_id,\n mcp_path: summary.mcp_path,\n tool_name: summary.tool_name\n}}];\n"
},
"id": "finalize-response",
"name": "Finalize Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4288,
-304
]
},
{
"parameters": {
"url": "{{SUPABASE_URL}}/rest/v1/mcp_registry?select=server_name,path,mcp_url,description,tools&active=eq.true&order=created_at.asc",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "apikey",
"value": "{{SUPABASE_SERVICE_KEY}}"
},
{
"name": "Authorization",
"value": "Bearer {{SUPABASE_SERVICE_KEY}}"
}
]
},
"options": {}
},
"id": "fetch-registry",
"name": "Fetch Registry",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
3616,
-304
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "\nconst servers = $input.all().map(i => i.json);\nif (!servers || !servers.length) {\n return [{ json: { instructions: null, serverCount: 0 } }];\n}\n\nconst lines = servers.map(s => {\n const tools = Array.isArray(s.tools) ? s.tools.join(', ') : (s.tools || '?');\n return `- ${s.server_name}: ${s.mcp_url} (Tool: ${tools}) \u2014 ${s.description || ''}`;\n});\n\nconst instructions = `Du hast MCP (Model Context Protocol) F\u00e4higkeiten:\n\n## MCP Client (mcp_client tool)\nDamit rufst du Tools auf MCP Servern auf. Parameter:\n- mcp_url: URL des MCP Servers\n- tool_name: Name des Tools\n- arguments: JSON object mit Tool-Parametern\n\n## MCP Builder (mcp_builder tool)\nDamit baust du NEUE MCP Server Workflows. Parameter:\n- task: Was der Server k\u00f6nnen soll\n\n## Aktuell verf\u00fcgbare MCP Server (${servers.length} total):\n${lines.join('\\n')}\n\n## Registry\nAlle aktiven Server: SELECT * FROM mcp_registry WHERE active = true;\nNeue Server werden automatisch eingetragen nach dem Build.`;\n\nreturn [{ json: { instructions, serverCount: servers.length } }];\n"
},
"id": "build-instructions",
"name": "Build Instructions",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3840,
-304
]
},
{
"parameters": {
"method": "PATCH",
"url": "{{SUPABASE_URL}}/rest/v1/agents?key=eq.mcp_instructions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "apikey",
"value": "{{SUPABASE_SERVICE_KEY}}"
},
{
"name": "Authorization",
"value": "Bearer {{SUPABASE_SERVICE_KEY}}"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Prefer",
"value": "return=minimal"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"content\": $json.instructions } }}",
"options": {}
},
"id": "update-agent-config",
"name": "Update Agent Config",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
4064,
-304
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "\nconst activated = $('Patch Tool Schema').first().json;\nconst assembled = $('Assemble & Deploy').first().json;\nconst workflowId = activated.id;\nconst path = assembled.path;\nconst toolName = assembled.toolName;\nlet sampleArgs;\ntry { sampleArgs = JSON.parse(assembled.sampleArgs || '{}'); } catch(e) { sampleArgs = {}; }\nconst mcpUrl = '{{N8N_URL}}/mcp/' + path;\n\ntry {\n const init = await helpers.httpRequest({\n method:'POST', url:mcpUrl,\n headers:{'Content-Type':'application/json','Accept':'application/json, text/event-stream'},\n body:JSON.stringify({jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'test',version:'1.0'}}}),\n returnFullResponse:true, encoding:'utf-8', json:false\n });\n const sid = init.headers['mcp-session-id'];\n if (!sid) return [{json:{success:false,error:'Keine mcp-session-id im Init-Response \u2014 MCP Trigger hat nicht geantwortet',workflowId,toolName,path,sampleArgs:assembled.sampleArgs}}];\n await helpers.httpRequest({method:'POST',url:mcpUrl,headers:{'Content-Type':'application/json','Accept':'application/json, text/event-stream','mcp-session-id':sid},body:JSON.stringify({jsonrpc:'2.0',method:'notifications/initialized'}),json:false});\n const resp = await helpers.httpRequest({method:'POST',url:mcpUrl,headers:{'Content-Type':'application/json','Accept':'application/json, text/event-stream','mcp-session-id':sid},body:JSON.stringify({jsonrpc:'2.0',id:2,method:'tools/call',params:{name:toolName,arguments:sampleArgs}}),json:false});\n const dataLine = String(resp).split('\\n').find(l => l.startsWith('data: '));\n if (!dataLine) return [{json:{success:false,error:'Keine Antwort: '+String(resp).substring(0,200),workflowId,toolName,path,sampleArgs:assembled.sampleArgs}}];\n const result = JSON.parse(dataLine.substring(6));\n const content = result.result?.content?.[0]?.text || '';\n if (content.includes('Fehler:') || content.includes('Error:') || result.error) {\n return [{json:{success:false,error:content||JSON.stringify(result.error),workflowId,toolName,path,sampleArgs:assembled.sampleArgs}}];\n }\n return [{json:{success:true,response:content,workflowId,toolName,path}}];\n} catch(e) {\n return [{json:{success:false,error:e.message,workflowId,toolName,path,sampleArgs:assembled.sampleArgs}}];\n}\n"
},
"id": "test-mcp",
"name": "Test MCP",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1920,
-304
],
"continueOnFail": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.success }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "test-passed",
"name": "Test Passed?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
2144,
-304
]
},
{
"parameters": {
"jsCode": "\nconst test = $input.first().json;\nconst assembled = $('Assemble & Deploy').first().json;\n\nlet schemaStr = '';\ntry { schemaStr = JSON.stringify(JSON.parse(test.inputSchema || '{}'), null, 2); } catch(e) { schemaStr = test.inputSchema || '{}'; }\n\nconst prompt = `Ein n8n MCP Server Tool hat einen Fehler beim Test:\n\nFEHLER: ${test.error}\n\nTOOL NAME: ${test.toolName}\nTEST PARAMETER (aus input_schema generiert): ${test.sampleArgs}\n\nAKTUELLES INPUT_SCHEMA:\n${schemaStr}\n\nAKTUELLER JSCODE:\n${assembled.originalJsCode}\n\nANALYSIERE den Fehler. M\u00f6gliche Ursachen:\n- Falscher API Endpoint\n- Falsche Parameter-Namen (jsCode \u2260 input_schema properties)\n- Fehlende Fehlerbehandlung\n- API returnt anderes Format als erwartet\n\nGib den korrigierten JavaScript Code zur\u00fcck.\nREGELN:\n- Parameter lesen: query.param_name (MUSS mit input_schema properties \u00fcbereinstimmen)\n- HTTP: await helpers.httpRequest({method:'GET', url:'...', headers:{...}})\n- KEIN fetch(), KEIN require(), KEIN new URL()\n- Return: String (kein JSON-Objekt)\n\nAntworte NUR mit dem korrigierten JavaScript Code (kein Markdown, kein JSON, reiner JS Code)!`;\n\nreturn [{json:{prompt, toolName:test.toolName, workflowId:test.workflowId, path:test.path, sampleArgs:test.sampleArgs, inputSchema:test.inputSchema}}];\n"
},
"id": "build-fix-prompt",
"name": "Build Fix Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2368,
-180
]
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.prompt }}"
},
"id": "fix-llm",
"name": "Fix LLM",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.4,
"position": [
2592,
-180
]
},
{
"parameters": {
"model": {
"__rl": true,
"value": "claude-opus-4-6",
"mode": "list"
},
"options": {}
},
"id": "fix-model",
"name": "Fix Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"typeVersion": 1.3,
"position": [
2664,
44
],
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "\nconst llmOutput = $input.first().json.text || '';\nconst meta = $('Build Fix Prompt').first().json;\nconst N8N_KEY = '{{N8N_API_KEY}}';\n\nlet newJsCode = (llmOutput || '').trim().replace(/^```(javascript|js)?\\n?/, '').replace(/\\n?```$/, '').trim();\nif (!newJsCode) return [{json:{success:false,error:'LLM gab leeren Fix zur\u00fcck',workflowId:meta.workflowId,path:meta.path,fixApplied:false}}];\n\n// fetch + patch workflow\nconst wf = await helpers.httpRequest({\n method:'GET', url:`{{N8N_URL}}/api/v1/workflows/${meta.workflowId}`,\n headers:{'X-N8N-API-KEY':N8N_KEY,'Content-Type':'application/json'}\n});\nconst nodes = wf.nodes.map(n => {\n if (n.parameters && n.parameters.name === meta.toolName) {\n n.parameters.jsCode = newJsCode;\n n.parameters.specifyInputSchema = false;\n }\n return n;\n});\nawait helpers.httpRequest({\n method:'PUT', url:`{{N8N_URL}}/api/v1/workflows/${meta.workflowId}`,\n headers:{'X-N8N-API-KEY':N8N_KEY,'Content-Type':'application/json'},\n body:JSON.stringify({name:wf.name, nodes, connections:wf.connections, settings:wf.settings||{}})\n});\n\n// retest\nconst mcpUrl = '{{N8N_URL}}/mcp/' + meta.path;\nlet sampleArgs;\ntry { sampleArgs = JSON.parse(meta.schemaExample); } catch(e) { sampleArgs = {}; }\ntry {\n const init = await helpers.httpRequest({\n method:'POST',url:mcpUrl,\n headers:{'Content-Type':'application/json','Accept':'application/json, text/event-stream'},\n body:JSON.stringify({jsonrpc:'2.0',id:1,method:'initialize',params:{protocolVersion:'2024-11-05',capabilities:{},clientInfo:{name:'test',version:'1.0'}}}),\n returnFullResponse:true,encoding:'utf-8',json:false\n });\n const sid = init.headers['mcp-session-id'];\n if (!sid) return [{json:{success:false,error:'Kein session ID nach Fix',fixApplied:true,workflowId:meta.workflowId,path:meta.path,toolName:meta.toolName}}];\n await helpers.httpRequest({method:'POST',url:mcpUrl,headers:{'Content-Type':'application/json','Accept':'application/json, text/event-stream','mcp-session-id':sid},body:JSON.stringify({jsonrpc:'2.0',method:'notifications/initialized'}),json:false});\n const resp = await helpers.httpRequest({method:'POST',url:mcpUrl,headers:{'Content-Type':'application/json','Accept':'application/json, text/event-stream','mcp-session-id':sid},body:JSON.stringify({jsonrpc:'2.0',id:2,method:'tools/call',params:{name:meta.toolName,arguments:sampleArgs}}),json:false});\n const dataLine = String(resp).split('\\n').find(l => l.startsWith('data: '));\n if (!dataLine) return [{json:{success:false,error:'Kein data nach Fix',fixApplied:true,workflowId:meta.workflowId,path:meta.path,toolName:meta.toolName}}];\n const result = JSON.parse(dataLine.substring(6));\n const content = result.result?.content?.[0]?.text || '';\n const ok = !content.startsWith('Fehler:') && !content.startsWith('Error:') && !result.error;\n return [{json:{success:ok,response:content,fixApplied:true,workflowId:meta.workflowId,path:meta.path,toolName:meta.toolName}}];\n} catch(e) {\n return [{json:{success:false,error:'Retest: '+e.message,fixApplied:true,workflowId:meta.workflowId,path:meta.path,toolName:meta.toolName}}];\n}\n"
},
"id": "patch-retest",
"name": "Patch & Retest",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2944,
-180
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "\nconst mcpCreated = $('Create MCP Workflow').first().json;\nconst assembled = $('Assemble & Deploy').first().json;\nconst subId = $('Create Sub-Workflow').first().json.id;\nconst testResult = $input.first().json;\n\nconst testStatus = testResult.success\n ? `\u2705 Test OK: ${(testResult.response||'').substring(0,100)}`\n : `\u274c Test fehlgeschlagen: ${(testResult.error||'').substring(0,100)}`;\n\nreturn [{json:{\n response: `MCP Server erstellt!\\nMCP Workflow: ${mcpCreated.name} (ID: ${mcpCreated.id})\\nSub-Workflow ID: ${subId}\\nMCP-Path: /mcp/${assembled.path}\\nTool: ${assembled.toolName}\\nURL: {{N8N_URL}}/mcp/${assembled.path}\\n\\n${testStatus}`,\n workflow_id: mcpCreated.id,\n sub_workflow_id: subId,\n mcp_path: assembled.path,\n tool_name: assembled.toolName,\n test_success: testResult.success\n}}];\n"
},
"id": "result",
"name": "Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3168,
-304
]
},
{
"id": "patch-tool-schema",
"name": "Patch Tool Schema",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1008,
-200
],
"parameters": {
"jsCode": "\nconst subId = $('Create Sub-Workflow').first().json.id;\nconst KEY = '{{N8N_API_KEY}}';\nconst H = {'X-N8N-API-KEY': KEY};\nawait helpers.httpRequest({method:'POST', url:`{{N8N_INTERNAL_URL}}/api/v1/workflows/${subId}/activate`, headers:H});\nreturn [{json: {subId, activated: true}}];\n"
}
},
{
"id": "build-mcp-json",
"name": "Build MCP JSON",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1264,
0
],
"parameters": {
"jsCode": "\nconst subId = $('Create Sub-Workflow').first().json.id;\nconst assembled = $('Assemble & Deploy').first().json;\nconst toolName = assembled.toolName;\nconst description = assembled.description;\nconst serverName = assembled.serverName;\nconst path = assembled.path;\nconst workflowInputsValue = JSON.parse(assembled.workflowInputsValue || '{}');\nconst workflowInputsSchema = JSON.parse(assembled.workflowInputsSchema || '[]');\n\nconst mcpWorkflow = {\n name: 'MCP: ' + serverName,\n settings: {},\n nodes: [\n {\n id: 'mcp-trigger',\n name: 'MCP Server Trigger',\n type: '@n8n/n8n-nodes-langchain.mcpTrigger',\n typeVersion: 2,\n position: [0, 0],\n parameters: {path: path, authentication: 'none'}\n },\n {\n id: 'tool-wf',\n name: toolName,\n type: '@n8n/n8n-nodes-langchain.toolWorkflow',\n typeVersion: 2.2,\n position: [0, 300],\n parameters: {\n name: toolName,\n description: description,\n workflowId: {__rl: true, value: subId, mode: 'id'},\n workflowInputs: {\n mappingMode: 'defineBelow',\n value: workflowInputsValue,\n matchingColumns: [],\n schema: workflowInputsSchema,\n attemptToConvertTypes: false,\n convertFieldsToString: false\n }\n }\n }\n ],\n connections: {}\n};\nmcpWorkflow.connections[toolName] = {ai_tool: [[{node: 'MCP Server Trigger', type: 'ai_tool', index: 0}]]};\n\nreturn [{json: {mcpWorkflowJson: JSON.stringify(mcpWorkflow), subId, toolName, path, serverName}}];\n"
}
}
]
}
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.
anthropicApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
๐๏ธ MCP Builder. Uses httpRequest, executeWorkflowTrigger, chainLlm, lmChatAnthropic. Event-driven trigger; 24 nodes.
Source: https://github.com/freddy-schuetz/dmo-claw/blob/26e19d91f6d134dc61461e9059855ef6e48da228/workflows/mcp-builder.json โ original creator credit. Request a take-down โ
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 91 nodes.
This intelligent workflow automatically discovers and analyzes recently funded startups by: Monitoring multiple news sources (TechCrunch and VentureBeat) for funding announcements Using AI to extract
A friendly, practical tool that makes working with AppSheet data simpler and more efficient. This workflow is your go-to helper for building precise queries without getting lost in a sea of different
Episode 11: AI shorts factory app. Uses httpRequest, googleSheets, lmChatOpenAi, lmChatOllama. Event-driven trigger; 96 nodes.
This workflow automates Invoice & Payment Tracking (with Approvals) across Notion and Slack. Ingest โ You drop invoices/receipts (PDF/IMG/JSON) into the flow. Extract โ OCR + parsing pulls out key fie