{
  "name": "OpenShift Performance Metrics - MCP",
  "nodes": [
    {
      "parameters": {},
      "id": "trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        180,
        300
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ns",
              "name": "namespace",
              "value": "openshift-lightspeed",
              "type": "string"
            },
            {
              "id": "email",
              "name": "destinationEmail",
              "value": "maximiliano.pizarro.5@gmail.com",
              "type": "string"
            },
            {
              "id": "mcpHost",
              "name": "mcpHost",
              "value": "field-content-ols.openshift-lightspeed.svc.cluster.local",
              "type": "string"
            },
            {
              "id": "mcpPort",
              "name": "mcpPort",
              "value": "8080",
              "type": "string"
            },
            {
              "id": "tool",
              "name": "toolName",
              "value": "getPerformanceMetrics",
              "type": "string"
            },
            {
              "id": "litellmHost",
              "name": "litellmHost",
              "value": "field-content-ols-litellm.openshift-lightspeed.svc.cluster.local",
              "type": "string"
            },
            {
              "id": "litellmPort",
              "name": "litellmPort",
              "value": "4000",
              "type": "string"
            },
            {
              "id": "litellmApiKey",
              "name": "litellmApiKey",
              "value": "sk-openshift-mcp-1234",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "set-params",
      "name": "Set Parameters",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        400,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const http = require('http');\n\nfunction mcpRequest(host, port, body, sessionId) {\n  return new Promise((resolve, reject) => {\n    const data = JSON.stringify(body);\n    const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream', 'Content-Length': Buffer.byteLength(data) };\n    if (sessionId) headers['Mcp-Session-Id'] = sessionId;\n    const req = http.request({ hostname: host, port, path: '/mcp', method: 'POST', headers }, (res) => {\n      let body = '';\n      res.on('data', (chunk) => body += chunk);\n      res.on('end', () => {\n        const lines = body.split('\\n');\n        let jsonData = '';\n        for (const line of lines) { if (line.startsWith('data: ')) jsonData = line.substring(6); }\n        resolve({ status: res.statusCode, headers: res.headers, body: jsonData || body });\n      });\n    });\n    req.on('error', reject);\n    req.write(data);\n    req.end();\n  });\n}\n\nasync function callMcpTool(host, port, toolName, toolArgs) {\n  const init = await mcpRequest(host, port, { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'n8n-workflow', version: '1.0' } } });\n  const sid = init.headers['mcp-session-id'];\n  await mcpRequest(host, port, { jsonrpc: '2.0', method: 'notifications/initialized' }, sid);\n  const result = await mcpRequest(host, port, { jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: toolName, arguments: toolArgs } }, sid);\n  const parsed = JSON.parse(result.body);\n  return parsed.result?.content?.[0]?.text || 'Error: ' + JSON.stringify(parsed.error);\n}\n\nconst j = $input.first().json;\nconst host = j.mcpHost;\nconst port = parseInt(j.mcpPort, 10);\nconst mcpOutput = await callMcpTool(host, port, j.toolName, { namespace: j.namespace });\nreturn [{ json: { ...j, mcpOutput } }];\n"
      },
      "id": "mcp-call",
      "name": "MCP Tool Call",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        620,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const http = require('http');\nconst input = $input.first().json;\nconst litellmHost = input.litellmHost;\nconst litellmPort = parseInt(input.litellmPort, 10);\nconst litellmApiKey = input.litellmApiKey || '';\nconst toolName = input.toolName;\nconst namespace = input.namespace;\nconst mcpOutputFull = input.mcpOutput || '';\nconst mcpOutput = mcpOutputFull.length > 800 ? mcpOutputFull.substring(0, 800) + '\\n[... truncated for AI analysis, see Raw MCP Output below for full data]' : mcpOutputFull;\nconst systemPrompt = 'Analyze this OpenShift MCP output for tool \"' + toolName + '\" in namespace \"' + namespace + '\". Reply with: 1) HTML table of key data, 2) Health status (Healthy/Warning/Critical). Be concise, use inline CSS.';\nconst requestBody = JSON.stringify({\n  model: 'granite',\n  messages: [\n    { role: 'system', content: systemPrompt },\n    { role: 'user', content: mcpOutput }\n  ],\n  temperature: 0.3,\n  max_tokens: 1024\n});\nreturn new Promise((resolve, reject) => {\n  const req = http.request({\n    hostname: litellmHost,\n    port: litellmPort,\n    path: '/v1/chat/completions',\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'Authorization': 'Bearer ' + litellmApiKey,\n      'Content-Length': Buffer.byteLength(requestBody)\n    }\n  }, (res) => {\n    let data = '';\n    res.on('data', (chunk) => { data += chunk; });\n    res.on('end', () => {\n      try {\n        const parsed = JSON.parse(data);\n        let rawAI = parsed.choices?.[0]?.message?.content || 'AI analysis unavailable';\n        rawAI = rawAI.replace(/<think>[\\s\\S]*?<\\/think>/g, '').trim();\n        const aiAnalysis = rawAI.length > 1800 ? rawAI.substring(0, 1800) + '\n[... truncated ...]' : rawAI;\n        const model = parsed.model || 'deepseek-r1-distill-qwen-14b';\n        resolve([{ json: { ...input, aiAnalysis, aiModel: model } }]);\n      } catch (e) {\n        resolve([{ json: { ...input, aiAnalysis: 'AI analysis failed: ' + e.message, aiModel: 'error' } }]);\n      }\n    });\n  });\n  req.on('error', (e) => {\n    resolve([{ json: { ...input, aiAnalysis: 'AI service unavailable: ' + e.message, aiModel: 'unreachable' } }]);\n  });\n  req.write(requestBody);\n  req.end();\n});\n"
      },
      "id": "ai-format",
      "name": "AI Format & Explain",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        840,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const http = require('http');\nconst input = $input.first().json;\nconst namespace = input.namespace;\nconst email = input.destinationEmail;\nconst toolName = input.toolName;\nconst mcpOutput = input.mcpOutput || '';\nconst aiAnalysis = input.aiAnalysis || '';\nconst aiModel = input.aiModel || 'granite';\nconst ts = new Date().toISOString();\nconst title = \"Performance Metrics Report\";\nconst subtitle = \"Resource usage and performance monitoring via MCP\";\nconst escapedOutput = mcpOutput.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\nconst html = `<html><body style=\"margin:0;padding:0;background:#f0f0f0;font-family:'Red Hat Display',Arial,sans-serif;\">\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f0f0f0;\"><tr><td align=\"center\" style=\"padding:24px;\">\n<table width=\"640\" style=\"max-width:640px;width:100%;\">\n<tr><td style=\"background:#151515;padding:16px 32px;border-radius:8px 8px 0 0;\">\n<span style=\"color:#EE0000;font-size:20px;font-weight:700;\">Red Hat</span>\n<span style=\"float:right;color:#C6C6C6;font-size:12px;line-height:28px;\">OpenShift Developer Sandbox</span></td></tr>\n<tr><td style=\"background:#DC382D;padding:28px 32px;\">\n<h1 style=\"margin:0;font-size:24px;color:#FFF;\">${title}</h1>\n<p style=\"margin:6px 0 0;font-size:14px;color:rgba(255,255,255,.85);\">${subtitle}</p></td></tr>\n<tr><td style=\"background:#292929;padding:0;\">\n<table width=\"100%\"><tr>\n<td style=\"padding:12px 16px;text-align:center;color:#A3A3A3;font-size:11px;border-right:1px solid #3C3C3C;width:25%;\">\n<b style=\"display:block;color:#FFF;font-size:13px;\">${namespace}</b>Namespace</td>\n<td style=\"padding:12px 16px;text-align:center;color:#A3A3A3;font-size:11px;border-right:1px solid #3C3C3C;width:25%;\">\n<b style=\"display:block;color:#FFF;font-size:13px;\">${ts}</b>Generated</td>\n<td style=\"padding:12px 16px;text-align:center;color:#A3A3A3;font-size:11px;border-right:1px solid #3C3C3C;width:25%;\">\n<b style=\"display:block;color:#FFF;font-size:13px;\">${toolName}</b>MCP Tool</td>\n<td style=\"padding:12px 16px;text-align:center;color:#A3A3A3;font-size:11px;width:25%;\">\n<b style=\"display:block;color:#FFF;font-size:13px;\">${aiModel}</b>AI Model</td>\n</tr></table></td></tr>\n<tr><td style=\"background:#FFF;padding:28px 32px;\">\n<h2 style=\"margin:0 0 16px;font-size:17px;border-bottom:2px solid #DC382D;padding-bottom:10px;\">AI Analysis</h2>\n<div style=\"padding:8px 0;font-size:14px;line-height:1.6;\">${aiAnalysis}</div>\n</td></tr>\n<tr><td style=\"background:#FFF;padding:0 32px 28px 32px;\">\n<h2 style=\"margin:0 0 16px;font-size:17px;border-bottom:2px solid #292929;padding-bottom:10px;color:#6A6E73;\">Raw MCP Output</h2>\n<pre style=\"background:#1E1E1E;color:#D4D4D4;padding:16px;border-radius:6px;overflow-x:auto;font-size:12px;line-height:1.5;white-space:pre-wrap;word-wrap:break-word;\">${escapedOutput}</pre>\n</td></tr>\n<tr><td style=\"background:#F5F5F5;padding:16px 32px;border-top:1px solid #E0E0E0;text-align:center;\">\n<span style=\"display:inline-block;background:#EE0000;color:white;font-size:10px;font-weight:700;padding:4px 10px;border-radius:3px;\">RED HAT</span>\n<span style=\"font-size:11px;color:#6A6E73;margin:0 8px;\">OpenShift MCP Server</span>\n<span style=\"font-size:11px;color:#DC382D;font-weight:600;margin:0 8px;\">n8n</span>\n</td></tr>\n<tr><td style=\"background:#151515;padding:20px 32px;border-radius:0 0 8px 8px;text-align:center;\">\n<p style=\"margin:0;font-size:12px;color:#A3A3A3;\">Auto-generated by n8n on <b style=\"color:#FFF;\">Red Hat OpenShift Developer Sandbox</b></p>\n</td></tr></table></td></tr></table></body></html>`;\nconst subject = `[Red Hat OpenShift] ${title} | ${namespace} | ${ts}`;\nconst mailBody = JSON.stringify({\n  From: { Name: 'n8n Workflow', Email: 'workflow@n8n.local' },\n  To: [{ Name: 'Admin', Email: email }],\n  Subject: subject,\n  HTML: html\n});\nreturn new Promise((resolve, reject) => {\n  const req = http.request({\n    hostname: 'n8n-mailpit',\n    port: 8025,\n    path: '/api/v1/send',\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'Content-Length': Buffer.byteLength(mailBody)\n    }\n  }, (res) => {\n    let data = '';\n    res.on('data', (chunk) => { data += chunk; });\n    res.on('end', () => {\n      resolve([{ json: { mailpitStatus: res.statusCode, mailpitResponse: data, subject } }]);\n    });\n  });\n  req.on('error', reject);\n  req.write(mailBody);\n  req.end();\n});\n"
      },
      "id": "report-email",
      "name": "Build Report & Send Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1060,
        300
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Set Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Parameters": {
      "main": [
        [
          {
            "node": "MCP Tool Call",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MCP Tool Call": {
      "main": [
        [
          {
            "node": "AI Format & Explain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Format & Explain": {
      "main": [
        [
          {
            "node": "Build Report & Send Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "active": false
}