{
  "name": "Generic MCP Technical Report Workflow",
  "nodes": [
    {
      "parameters": {
        "path": "8f2c5b7a-6d4e-4d2f-9f1b-0c2a4d8e6f31"
      },
      "type": "@n8n/n8n-nodes-langchain.mcpTrigger",
      "typeVersion": 2,
      "position": [
        -720,
        32
      ],
      "id": "2b6803b2-cd3f-41db-bdbc-f93858e360e0",
      "name": "MCP Server Trigger",
      "webhookId": "8f2c5b7a-6d4e-4d2f-9f1b-0c2a4d8e6f31"
    },
    {
      "parameters": {
        "description": "Builds a generic structured technical report or incident documentation pack in Spanish. Use this for any technical topic, incident, diagnosis, internal note, troubleshooting case, implementation summary, architecture documentation, postmortem draft or support report. The tool does not decide the domain-specific content: the caller should provide topic/problem/context, environment, expected, actual, commands, hypotheses, diagnostic steps, remediation steps, risks and validation criteria. The workflow normalizes the input, classifies severity/category, builds metadata, generates a consistent Markdown skeleton, and returns JSON ready to save with Filesystem MCP. It performs no web search and does not verify external facts.",
        "jsCode": "// Generic n8n Custom Code Tool for MCP Server Trigger.\n// Tool input is available in the `query` variable.\n// This tool is intentionally generic: it structures technical documentation,\n// but the topic-specific content must come from the MCP client/model prompt.\n\nfunction parseInput(raw) {\n  if (typeof raw === 'object' && raw !== null) return raw;\n  if (typeof raw !== 'string') return {};\n  const trimmed = raw.trim();\n  if (!trimmed) return {};\n  try {\n    return JSON.parse(trimmed);\n  } catch (error) {\n    return { topic: trimmed, problem: trimmed };\n  }\n}\n\nfunction slugify(value) {\n  return String(value || '')\n    .toLowerCase()\n    .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/(^-|-$)/g, '')\n    .slice(0, 90) || 'technical-report';\n}\n\nfunction unique(values) {\n  return [...new Set(\n    values\n      .filter(Boolean)\n      .map((value) => String(value).trim())\n      .filter(Boolean)\n  )];\n}\n\nfunction nowIso() {\n  return new Date().toISOString();\n}\n\nfunction toArray(value, fallback = []) {\n  if (Array.isArray(value)) return value;\n  if (typeof value === 'string' && value.trim()) {\n    return value\n      .split(/\\n|;/)\n      .map((item) => item.trim())\n      .filter(Boolean);\n  }\n  return fallback;\n}\n\nfunction stringifyMaybe(value) {\n  if (value === undefined || value === null || value === '') return '';\n  if (typeof value === 'string') return value;\n  return JSON.stringify(value, null, 2);\n}\n\nfunction codeBlock(value, language = '') {\n  const content = stringifyMaybe(value).trim();\n  if (!content) return '';\n  return `\\`\\`\\`${language}\\n${content}\\n\\`\\`\\``;\n}\n\nfunction yamlArray(values) {\n  return `[ ${unique(values).map((value) => `\"${String(value).replace(/\"/g, '\\\\\"')}\"`).join(', ')} ]`;\n}\n\nfunction detectSeverity(text, explicitSeverity) {\n  if (explicitSeverity) return String(explicitSeverity).toLowerCase();\n  const value = String(text || '').toLowerCase();\n\n  if (/(produccion|production|caido|down|outage|pérdida de datos|perdida de datos|data loss|seguridad|security|breach|crítico|critico|critical|bloqueante|blocker)/.test(value)) {\n    return 'high';\n  }\n\n  if (/(error|warning|lento|slow|timeout|hang|no conecta|connection|permisos|permission|docker|mcp|workflow|api|deploy|build|crash|fallo)/.test(value)) {\n    return 'medium';\n  }\n\n  return 'low';\n}\n\nfunction detectCategory(text, explicitCategory) {\n  if (explicitCategory) return String(explicitCategory);\n  const value = String(text || '').toLowerCase();\n\n  const rules = [\n    ['docker', /(docker|compose|contenedor|container|imagen|volume|volumen)/],\n    ['linux', /(linux|ubuntu|debian|permisos|chmod|chown|systemd|servicio|terminal|shell)/],\n    ['api', /(api|http|webhook|endpoint|request|response|json|oauth|token)/],\n    ['database', /(postgres|mysql|sqlite|database|base de datos|sql|migration|migracion)/],\n    ['mobile-dev', /(flutter|android|ios|emulador|avd|xcode|gradle|apk|ipa)/],\n    ['frontend', /(react|vue|svelte|astro|vite|css|frontend|landing|web)/],\n    ['backend', /(node|express|fastapi|nestjs|backend|server|servidor)/],\n    ['automation', /(n8n|workflow|automatización|automation|cron|pipeline)/],\n    ['ai-tools', /(ia|ai|llm|modelo|mcp|lm studio|openai|qwen|gemma|agent|agente)/],\n    ['security', /(seguridad|security|vulnerabilidad|malware|credential|token|secreto|secret)/],\n  ];\n\n  const match = rules.find(([, regex]) => regex.test(value));\n  return match ? match[0] : 'technical-documentation';\n}\n\nfunction severityMetadata(severity) {\n  const map = {\n    low: {\n      label: 'Baja',\n      meaning: 'No bloquea el trabajo principal, pero conviene documentarlo y resolverlo.',\n      priority: 'Normal'\n    },\n    medium: {\n      label: 'Media',\n      meaning: 'Afecta al flujo de trabajo o bloquea una parte concreta del proceso.',\n      priority: 'Prioritaria'\n    },\n    high: {\n      label: 'Alta',\n      meaning: 'Bloquea el uso principal, afecta a producción o puede implicar riesgo operativo.',\n      priority: 'Urgente'\n    },\n    critical: {\n      label: 'Crítica',\n      meaning: 'Impacto severo, posible caída general, pérdida de datos o riesgo de seguridad.',\n      priority: 'Inmediata'\n    }\n  };\n  return map[severity] || map.medium;\n}\n\nfunction normalizeSteps(rawSteps, fallbackLabels) {\n  if (Array.isArray(rawSteps) && rawSteps.length > 0) {\n    return rawSteps.map((step, index) => {\n      if (typeof step === 'string') {\n        return {\n          title: step,\n          action: step,\n          expected: 'Definir resultado esperado durante la revisión.',\n          evidence: ''\n        };\n      }\n\n      return {\n        title: step.title || step.step || `Paso ${index + 1}`,\n        action: step.action || step.command || step.description || '',\n        expected: step.expected || '',\n        evidence: step.evidence || ''\n      };\n    });\n  }\n\n  return fallbackLabels.map((title) => ({\n    title,\n    action: '',\n    expected: '',\n    evidence: ''\n  }));\n}\n\nfunction normalizeCommands(rawCommands) {\n  if (!rawCommands) return [];\n\n  if (Array.isArray(rawCommands)) {\n    return rawCommands.map((command, index) => {\n      if (typeof command === 'string') {\n        return {\n          title: `Comando ${index + 1}`,\n          language: 'bash',\n          code: command\n        };\n      }\n\n      return {\n        title: command.title || `Comando ${index + 1}`,\n        language: command.language || 'bash',\n        code: command.code || command.command || ''\n      };\n    });\n  }\n\n  if (typeof rawCommands === 'string') {\n    return rawCommands\n      .split(/\\n(?=[a-zA-Z0-9_$./-])/)\n      .map((command, index) => ({\n        title: `Comando ${index + 1}`,\n        language: 'bash',\n        code: command.trim()\n      }))\n      .filter((item) => item.code);\n  }\n\n  return [];\n}\n\nconst input = parseInput(query);\n\nconst topic = input.topic || input.title || input.problem || input.incident || 'Documentación técnica';\nconst problem = input.problem || input.incident || input.description || topic;\nconst context = input.context || input.environment || '';\nconst expected = input.expected || '';\nconst actual = input.actual || '';\nconst audience = input.audience || 'equipo técnico';\nconst owner = input.owner || 'Equipo técnico';\nconst status = input.status || 'draft';\nconst language = input.language || 'es';\nconst createdAt = input.created_at || nowIso();\n\nconst severity = detectSeverity(`${topic} ${problem} ${context} ${expected} ${actual}`, input.severity);\nconst category = detectCategory(`${topic} ${problem} ${context} ${expected} ${actual}`, input.category);\nconst severityInfo = severityMetadata(severity);\n\nconst slug = slugify(input.slug || topic);\nconst filename = input.output_filename || `technical-report-${slug}.md`;\n\nconst tags = unique([\n  ...(input.tags || []),\n  category,\n  severity,\n  'technical-report',\n  'documentation',\n  'troubleshooting'\n]);\n\nconst assumptions = toArray(input.assumptions, [\n  'La información del informe procede del contexto proporcionado por el usuario o por el modelo.',\n  'El informe debe ser revisado por una persona antes de usarse como documentación definitiva.'\n]);\n\nconst hypotheses = toArray(input.hypotheses, [\n  'Añadir hipótesis específicas según el contexto técnico proporcionado.',\n  'Priorizar hipótesis verificables mediante comandos, logs o pruebas reproducibles.'\n]);\n\nconst diagnosticSteps = normalizeSteps(input.diagnostic_steps, [\n  'Reproducir o describir el caso',\n  'Identificar entorno y alcance',\n  'Recopilar evidencias',\n  'Validar hipótesis',\n  'Aplicar corrección mínima',\n  'Verificar resultado'\n]);\n\nconst remediationSteps = normalizeSteps(input.remediation_steps || input.remediation_plan, [\n  'Definir acción correctiva principal',\n  'Aplicar cambio de forma controlada',\n  'Verificar comportamiento',\n  'Documentar resultado'\n]);\n\nconst validationChecklist = toArray(input.validation_checklist, [\n  'El problema o caso queda descrito con suficiente contexto.',\n  'El entorno afectado está identificado.',\n  'Las hipótesis son verificables.',\n  'Los pasos de diagnóstico son reproducibles.',\n  'Las acciones correctivas tienen criterios de validación.',\n  'El resultado final se ha comprobado.',\n  'El documento se ha guardado correctamente.'\n]);\n\nconst commands = normalizeCommands(input.commands);\nconst references = toArray(input.references, []);\nconst risks = toArray(input.risks, []);\nconst nextSteps = toArray(input.next_steps, []);\nconst configuration = input.configuration || input.config || {};\nconst architecture = input.architecture || input.flow || '';\n\nconst title = input.report_title || input.title || `Informe técnico: ${topic}`;\nconst summary = input.summary || `Informe técnico estructurado sobre: ${problem}`;\n\nconst frontmatter = `---\ntitle: \"${String(title).replace(/\"/g, '\\\\\"')}\"\ndescription: \"${String(summary).replace(/\"/g, '\\\\\"')}\"\ndate: ${createdAt}\nstatus: ${status}\nowner: ${owner}\nseverity: ${severity}\ncategory: ${category}\ntags: ${yamlArray(tags)}\n---`;\n\nconst diagnosticMarkdown = diagnosticSteps.map((item, index) => {\n  const evidence = item.evidence ? `\\n\\nEvidencia:\\n\\n${item.evidence}` : '';\n  const actionBlock = item.action ? `\\n\\nAcción:\\n\\n${codeBlock(item.action, item.action.includes('\\n') || /^[a-zA-Z0-9_$./-]+/.test(item.action) ? 'bash' : 'text')}` : '';\n  const expectedText = item.expected ? `\\n\\nResultado esperado:\\n\\n${item.expected}` : '';\n  return `### ${index + 1}. ${item.title}${actionBlock}${expectedText}${evidence}`;\n}).join('\\n\\n');\n\nconst remediationMarkdown = remediationSteps.map((item, index) => {\n  const actionBlock = item.action ? `\\n\\nAcción:\\n\\n${codeBlock(item.action, item.action.includes('\\n') || /^[a-zA-Z0-9_$./-]+/.test(item.action) ? 'bash' : 'text')}` : '';\n  const expectedText = item.expected ? `\\n\\nValidación:\\n\\n${item.expected}` : '';\n  return `### ${index + 1}. ${item.title}${actionBlock}${expectedText}`;\n}).join('\\n\\n');\n\nconst commandsMarkdown = commands.length\n  ? commands.map((command) => `### ${command.title}\\n\\n${codeBlock(command.code, command.language || 'bash')}`).join('\\n\\n')\n  : 'No se proporcionaron comandos específicos. Añadir comandos concretos si el caso lo requiere.';\n\nconst configurationMarkdown = Object.keys(configuration).length\n  ? codeBlock(configuration, 'json')\n  : 'No se proporcionó configuración específica. Añadir fragmentos de configuración si son relevantes.';\n\nconst architectureMarkdown = architecture\n  ? codeBlock(architecture, 'text')\n  : 'No se proporcionó arquitectura específica. Añadir diagrama o flujo si ayuda a entender el caso.';\n\nconst referencesMarkdown = references.length\n  ? references.map((ref) => `- ${ref}`).join('\\n')\n  : '- Añadir referencias internas, documentación oficial o enlaces revisados si procede.';\n\nconst markdownTemplate = `${frontmatter}\n\n# ${title}\n\n## Resumen\n\n${summary}\n\n## Contexto\n\n**Tema:** ${topic}\n\n**Problema o necesidad:** ${problem}\n\n**Audiencia:** ${audience}\n\n${context ? `**Entorno/contexto:** ${context}` : '**Entorno/contexto:** Pendiente de completar.'}\n\n${expected ? `**Resultado esperado:** ${expected}` : '**Resultado esperado:** Pendiente de completar.'}\n\n${actual ? `**Resultado observado:** ${actual}` : '**Resultado observado:** Pendiente de completar.'}\n\n## Clasificación\n\n- **Categoría:** ${category}\n- **Severidad:** ${severityInfo.label} (${severity})\n- **Prioridad:** ${severityInfo.priority}\n- **Significado:** ${severityInfo.meaning}\n- **Estado:** ${status}\n- **Responsable:** ${owner}\n\n## Supuestos\n\n${assumptions.map((item) => `- ${item}`).join('\\n')}\n\n## Arquitectura o flujo relacionado\n\n${architectureMarkdown}\n\n## Configuración relevante\n\n${configurationMarkdown}\n\n## Hipótesis\n\n${hypotheses.map((item) => `- ${item}`).join('\\n')}\n\n## Plan de diagnóstico\n\n${diagnosticMarkdown}\n\n## Comandos útiles\n\n${commandsMarkdown}\n\n## Plan de resolución\n\n${remediationMarkdown}\n\n## Riesgos y precauciones\n\n${risks.length ? risks.map((item) => `- ${item}`).join('\\n') : '- Añadir riesgos concretos si el caso afecta a producción, datos, seguridad o disponibilidad.'}\n\n## Checklist de validación\n\n${validationChecklist.map((item) => `- [ ] ${item}`).join('\\n')}\n\n## Siguientes pasos\n\n${nextSteps.length ? nextSteps.map((item) => `- ${item}`).join('\\n') : '- Completar revisión técnica.\\n- Validar el resultado en el entorno afectado.\\n- Actualizar documentación interna si procede.'}\n\n## Referencias\n\n${referencesMarkdown}\n\n## Notas de honestidad técnica\n\n- Este informe no realiza búsqueda web.\n- El contenido específico depende del contexto proporcionado por el usuario o por el modelo.\n- La herramienta estructura y normaliza la documentación, pero no verifica hechos externos.\n- Antes de cerrar el caso, conviene revisar comandos, rutas, evidencias y resultados.\n\nHappy Hacking!!\n`;\n\nconst result = {\n  ok: true,\n  tool: 'build_technical_report_pack',\n  language,\n  filename,\n  slug,\n  title,\n  summary,\n  metadata: {\n    topic,\n    problem,\n    category,\n    severity,\n    severity_label: severityInfo.label,\n    priority: severityInfo.priority,\n    status,\n    owner,\n    created_at: createdAt,\n    audience,\n    tags\n  },\n  normalized_input: {\n    topic,\n    problem,\n    context,\n    expected,\n    actual,\n    architecture,\n    configuration,\n    assumptions,\n    hypotheses,\n    diagnostic_steps: diagnosticSteps,\n    remediation_steps: remediationSteps,\n    commands,\n    risks,\n    validation_checklist: validationChecklist,\n    next_steps: nextSteps,\n    references\n  },\n  structure: {\n    sections: [\n      'Resumen',\n      'Contexto',\n      'Clasificación',\n      'Supuestos',\n      'Arquitectura o flujo relacionado',\n      'Configuración relevante',\n      'Hipótesis',\n      'Plan de diagnóstico',\n      'Comandos útiles',\n      'Plan de resolución',\n      'Riesgos y precauciones',\n      'Checklist de validación',\n      'Siguientes pasos',\n      'Referencias',\n      'Notas de honestidad técnica'\n    ],\n    frontmatter\n  },\n  markdown_template: markdownTemplate,\n  filesystem_note: 'Save markdown_template using this relative filename in the configured Filesystem MCP output directory.',\n  honesty_note: 'This workflow is generic. It structures technical documentation from provided input and does not hardcode a specific topic or verify external facts.'\n};\n\nreturn JSON.stringify(result, null, 2);"
      },
      "type": "@n8n/n8n-nodes-langchain.toolCode",
      "typeVersion": 1.3,
      "position": [
        -480,
        304
      ],
      "id": "3bc3e0be-c6ff-41a5-8d3a-96444932f384",
      "name": "build_technical_report_pack"
    },
    {
      "parameters": {
        "description": "Builds a compact generic Markdown technical note from any short technical topic, incident, task or problem description. Use this for quick MCP tests or lightweight internal notes. The tool is generic and does not assume any specific technology.",
        "jsCode": "function parseInput(raw) {\n  if (typeof raw === 'object' && raw !== null) return raw;\n  if (typeof raw !== 'string') return {};\n  const trimmed = raw.trim();\n  if (!trimmed) return {};\n  try {\n    return JSON.parse(trimmed);\n  } catch (error) {\n    return { text: trimmed, topic: trimmed };\n  }\n}\n\nfunction slugify(value) {\n  return String(value || '')\n    .toLowerCase()\n    .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/(^-|-$)/g, '')\n    .slice(0, 90) || 'technical-note';\n}\n\nconst input = parseInput(query);\nconst topic = input.topic || input.title || 'Nota técnica rápida';\nconst text = input.text || input.problem || input.incident || input.description || topic;\nconst context = input.context || input.environment || '';\nconst slug = slugify(input.slug || `${topic}-${text}`);\nconst filename = input.output_filename || `technical-note-${slug}.md`;\n\nconst markdown_template = `# ${topic}\n\n## Resumen\n\n${text}\n\n## Contexto\n\n${context || 'Pendiente de completar.'}\n\n## Checklist rápido\n\n- [ ] Describir el caso con claridad.\n- [ ] Identificar entorno afectado.\n- [ ] Recopilar evidencias o logs relevantes.\n- [ ] Definir hipótesis verificables.\n- [ ] Probar una acción mínima.\n- [ ] Validar resultado.\n- [ ] Guardar o compartir la nota si procede.\n\n## Notas\n\nEsta nota fue generada como estructura técnica rápida desde una herramienta MCP expuesta por n8n.\n`;\n\nreturn JSON.stringify({\n  ok: true,\n  tool: 'build_quick_technical_note',\n  title: topic,\n  filename,\n  markdown_template,\n  filesystem_note: 'Save markdown_template using this relative filename in the configured Filesystem MCP output directory.'\n}, null, 2);"
      },
      "type": "@n8n/n8n-nodes-langchain.toolCode",
      "typeVersion": 1.3,
      "position": [
        -672,
        304
      ],
      "id": "2a09a693-932c-4164-8a85-b8aec7f6b49d",
      "name": "build_quick_technical_note"
    },
    {
      "parameters": {
        "content": "## Generic MCP technical documentation pipeline\n\nThis workflow exposes two generic MCP tools to LM Studio or another MCP client:\n\n1. `build_technical_report_pack` — builds a full structured technical report pack from the topic/context supplied by the prompt.\n\n2. `build_quick_technical_note` — builds a compact Markdown technical note for quick tests or lightweight documentation.\n\nThe workflow does not hardcode a specific technology, incident or article topic. The MCP client/model should provide the domain-specific context.\n\nRecommended MCP URL after publishing:\n\n`http://localhost:5678/mcp/8f2c5b7a-6d4e-4d2f-9f1b-0c2a4d8e6f31`",
        "height": 440,
        "width": 540
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -240,
        -64
      ],
      "id": "a2102fbd-1c02-4e88-8793-9dd92107958b",
      "name": "Demo notes"
    }
  ],
  "pinData": {},
  "connections": {
    "build_technical_report_pack": {
      "ai_tool": [
        [
          {
            "node": "MCP Server Trigger",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "build_quick_technical_note": {
      "ai_tool": [
        [
          {
            "node": "MCP Server Trigger",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "90190a2b-baa2-4ec2-b61c-ee6283c42632",
  "meta": {
    "instanceId": "77aec28534e77e069fddf185fce4f968f3513f1be343c36536ff210c3c3c93d3"
  },
  "id": "C95fpefo1jEN1dsG",
  "tags": []
}