<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sergey Byvshev</title>
    <description>The latest articles on DEV Community by Sergey Byvshev (@javdet).</description>
    <link>https://dev.to/javdet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3830322%2F2225f9de-dfac-4620-9060-c7e6f1783e7d.jpeg</url>
      <title>DEV Community: Sergey Byvshev</title>
      <link>https://dev.to/javdet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/javdet"/>
    <language>en</language>
    <item>
      <title>MCP servers for the entire team: from local launch to centralized access</title>
      <dc:creator>Sergey Byvshev</dc:creator>
      <pubDate>Wed, 15 Apr 2026 06:50:14 +0000</pubDate>
      <link>https://dev.to/javdet/mcp-servers-for-the-entire-team-from-local-launch-to-centralized-access-20e7</link>
      <guid>https://dev.to/javdet/mcp-servers-for-the-entire-team-from-local-launch-to-centralized-access-20e7</guid>
      <description>&lt;p&gt;When you have six MCP servers and ten colleagues, "just run npx locally" stops working. Not everyone wants to install Node.js, managers don't have Docker, and your local &lt;code&gt;claude_desktop_config.json&lt;/code&gt; starts looking like a secrets vault for every production system.&lt;/p&gt;

&lt;p&gt;I went from remote MCP → local setup → Docker → Kubernetes with a universal Helm chart and JWT auth via Envoy. Here's what I hit along the way, what worked, and what's still unsolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 1: Remote MCP — When the Vendor Did the Work
&lt;/h2&gt;

&lt;p&gt;My first MCP experience was dead simple. I added the Atlassian MCP server to Claude as a remote MCP, authenticated, and it just worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"atlassian"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.atlassian.com/v1/sse"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem? Very few SaaS products offer this. Everything self-hosted or without native MCP support is a different story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 2: Local Setup — The Dependency Zoo
&lt;/h2&gt;

&lt;p&gt;Next, I wanted to connect my IDE to Kubernetes. No built-in MCP support here, so dependencies it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kubernetes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kubernetes-mcp-server@latest"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdkr883on91tcen8rfxd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdkr883on91tcen8rfxd.png" alt="claude-mcp" width="733" height="833"&gt;&lt;/a&gt;&lt;br&gt;
It worked, but one server needs Node.js, another needs Python and &lt;code&gt;uvx&lt;/code&gt;, a third needs a Go binary. The runtime zoo on your machine grows with every new MCP server. Not great when you're not even a developer.&lt;/p&gt;
&lt;h2&gt;
  
  
  Level 3: Docker — Isolation Without the Mess
&lt;/h2&gt;

&lt;p&gt;The logical next step — containers. Each MCP server with its own runtime, no host pollution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"grafana"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--rm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GRAFANA_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GRAFANA_SERVICE_ACCOUNT_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"grafana/mcp-grafana"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stdio"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GRAFANA_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://grafana.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GRAFANA_SERVICE_ACCOUNT_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;token&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For one engineer on one machine — enough. But when ten people need access, questions pile up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production tokens are scattered across laptops.&lt;/li&gt;
&lt;li&gt;Automated workflows (n8n, CI/CD) need MCP access too — and they run remotely.&lt;/li&gt;
&lt;li&gt;Managers and analysts want AI tools but aren't ready to deal with &lt;code&gt;docker run&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One conclusion: MCP servers need to move into shared infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 4: Kubernetes — Centralized Deployment
&lt;/h2&gt;

&lt;p&gt;The initial idea was straightforward: deploy remote MCP servers inside your infrastructure perimeter. At minimum, you can restrict access via corporate VPN.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3gf7ya3m83wgf0p0veg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3gf7ya3m83wgf0p0veg.png" alt="Balancing" width="800" height="310"&gt;&lt;/a&gt;&lt;br&gt;
Anyone who's tackled this has hit the same wall: most MCP servers communicate via stdio (stdin/stdout). You can't reach them over HTTP directly.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://github.com/michlyn/mcpgateway" rel="noopener noreferrer"&gt;MCP Gateway&lt;/a&gt; comes in — a proxy that translates Streamable HTTP to stdio and back.&lt;/p&gt;

&lt;p&gt;The flow: client (Claude Desktop, IDE, n8n) → HTTPS → Ingress → Kubernetes Service → Pod with MCP Gateway sidecar (HTTP → stdin) → MCP server process.&lt;/p&gt;
&lt;h3&gt;
  
  
  Universal Helm Chart
&lt;/h3&gt;

&lt;p&gt;To avoid writing manifests for every MCP server, I built a universal Helm chart: &lt;a href="https://artifacthub.io/packages/helm/mcp-helm-chart/mcp" rel="noopener noreferrer"&gt;mcp-helm-chart on ArtifactHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What it supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mode: proxy&lt;/code&gt;&lt;/strong&gt; — runs MCP Gateway as a sidecar, translating HTTP ↔ stdio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mode: native&lt;/code&gt;&lt;/strong&gt; — for servers that already support HTTP (no sidecar needed)&lt;/li&gt;
&lt;li&gt;Vault and ExternalSecrets integration for secrets management&lt;/li&gt;
&lt;li&gt;Gateway API and classic Ingress support&lt;/li&gt;
&lt;li&gt;HPA for horizontal scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Installation with Ingress-nginx (no auth):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add mcp https://javdet.github.io/mcp-helm-chart
helm &lt;span class="nb"&gt;install &lt;/span&gt;my-mcp mcp/mcp &lt;span class="nt"&gt;-f&lt;/span&gt; values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key sections of &lt;code&gt;values.yaml&lt;/code&gt; for deploying DigitalOcean MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;

&lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20-bookworm"&lt;/span&gt;
    &lt;span class="na"&gt;pullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
  &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@michlyn/mcpgateway"&lt;/span&gt;
    &lt;span class="na"&gt;stdioCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npx&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;@digitalocean/mcp&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--services&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apps,droplets,doks,networking"&lt;/span&gt;
    &lt;span class="na"&gt;outputTransport&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;streamable-http&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="na"&gt;httpPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/mcp&lt;/span&gt;

&lt;span class="c1"&gt;# Token stored in HashiCorp Vault, injected via Vault Webhook&lt;/span&gt;
&lt;span class="na"&gt;vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp"&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes_dev-fra1-01"&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DIGITALOCEAN_API_TOKEN&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault:devops/data/ai/mcp/digitalocean#token&lt;/span&gt;

&lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal"&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-buffering&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;off"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-http-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-read-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-send-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/use-regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/rewrite-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/$2&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aitool.example.com&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/digitalocean(/|$)(.*)&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ImplementationSpecific&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ssl-certificate&lt;/span&gt;
      &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aitool.example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g9mhx8zj4kq6ixa39s1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g9mhx8zj4kq6ixa39s1.png" alt="Ingress" width="800" height="647"&gt;&lt;/a&gt;&lt;br&gt;
MCP servers in Streamable HTTP mode are stateless. They scale horizontally with a standard HPA without any issues.&lt;/p&gt;

&lt;p&gt;The most pressing question here is authentication — or better yet, authorization. Most MCP servers don't support incoming authentication, so you have to handle it yourself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Authentication: JWT via Envoy
&lt;/h2&gt;

&lt;p&gt;Basic auth is barely better than nothing, so — straight to JWT. I used Envoy API Gateway since it natively supports JWT validation and was already in our stack.&lt;/p&gt;
&lt;h3&gt;
  
  
  Key and Token Generation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Generate RSA keys&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; mcp-jwt-private.pem 4096
openssl rsa &lt;span class="nt"&gt;-in&lt;/span&gt; mcp-jwt-private.pem &lt;span class="nt"&gt;-pubout&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; mcp-jwt-public.pem

&lt;span class="c"&gt;# 2. Generate Key ID&lt;/span&gt;
&lt;span class="nv"&gt;KID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 3. Build JWT header (base64url)&lt;/span&gt;
&lt;span class="nv"&gt;HEADER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;alg&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;RS256&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;typ&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;JWT&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;kid&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-w0&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'+/'&lt;/span&gt; &lt;span class="s1"&gt;'-_'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 4. Build JWT payload (1 year expiry)&lt;/span&gt;
&lt;span class="nv"&gt;PAYLOAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sub&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;claude-desktop&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;aud&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;mcp-servers&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;iss&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://your-domain.com&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;iat&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;exp&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;31536000&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-w0&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'+/'&lt;/span&gt; &lt;span class="s1"&gt;'-_'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 5. Sign&lt;/span&gt;
&lt;span class="nv"&gt;SIGNATURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HEADER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PAYLOAD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl dgst &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-sign&lt;/span&gt; mcp-jwt-private.pem &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-w0&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'+/'&lt;/span&gt; &lt;span class="s1"&gt;'-_'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 6. Final token&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HEADER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PAYLOAD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SIGNATURE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The public key is packaged into JWKS and stored in a ConfigMap. Envoy validates every incoming request by checking issuer, audience, and signature.&lt;/p&gt;

&lt;p&gt;Auth configuration in the chart values (Gateway API variant):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;gatewayApi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;parentRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-infra&lt;/span&gt;
      &lt;span class="na"&gt;sectionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;
  &lt;span class="na"&gt;hostnames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mcptools.example.com&lt;/span&gt;
  &lt;span class="na"&gt;timeouts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600s"&lt;/span&gt;
    &lt;span class="na"&gt;backendRequest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600s"&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PathPrefix&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/digitalocean&lt;/span&gt;
      &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;URLRewrite&lt;/span&gt;
          &lt;span class="na"&gt;urlRewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ReplacePrefixMatch&lt;/span&gt;
              &lt;span class="na"&gt;replacePrefixMatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jwt&lt;/span&gt;
    &lt;span class="na"&gt;jwt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcp-jwt-auth&lt;/span&gt;
          &lt;span class="na"&gt;issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcp-issuer&lt;/span&gt;
          &lt;span class="na"&gt;audiences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mcptools.example.com&lt;/span&gt;
          &lt;span class="na"&gt;localJWKS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ValueRef&lt;/span&gt;
            &lt;span class="na"&gt;valueRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
              &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jwks-config&lt;/span&gt;

&lt;span class="c1"&gt;# If you use External Secrets Operator, secrets can be fetched through it&lt;/span&gt;
&lt;span class="na"&gt;externalSecrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;refreshInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;
  &lt;span class="na"&gt;secretStoreRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterSecretStore&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;creationPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Owner&lt;/span&gt;
  &lt;span class="na"&gt;dataFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;extract&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infra/mcp/digitalocean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jjyhzjsz4ez5e00uplv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6jjyhzjsz4ez5e00uplv.png" alt="API gateway" width="800" height="803"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently, access to target systems (DigitalOcean, Grafana, Kubernetes) goes through a single service account. For read-only tasks — monitoring, diagnostics, fetching info — this is enough. For write operations, the question remains open..&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Access
&lt;/h2&gt;

&lt;p&gt;Periodic tasks (n8n workflows, CI/CD pipelines) connect to the same MCP servers over Streamable HTTP with separate service JWT tokens. The setup is identical — only the subject in the token payload differs, and optionally the access scope at the Gateway level.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Working, What's Not
&lt;/h2&gt;

&lt;p&gt;MCP tooling and infrastructure still have a few steps to take toward each other before usage becomes truly simple, reliable, and secure.&lt;/p&gt;

&lt;p&gt;The current setup works: six MCP servers in Kubernetes, one Helm chart, JWT auth via Envoy, secrets in Vault. Colleagues connect to remote MCP servers with zero local dependencies, automation uses the same endpoints.&lt;/p&gt;

&lt;p&gt;What's still missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-user authorization.&lt;/strong&gt; The MCP protocol doesn't support passing user context. We're living with service accounts for now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit logging.&lt;/strong&gt; Who called which tool with what parameters — not logged at the MCP level. You can collect this at the Envoy layer, but without call context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth standard.&lt;/strong&gt; Every vendor does it differently. OAuth, API Key, Bearer — no unified approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Helm chart is open source: &lt;a href="https://artifacthub.io/packages/helm/mcp-helm-chart/mcp" rel="noopener noreferrer"&gt;ArtifactHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How do you handle per-user authorization for MCP? We're still on a single service account — would love to hear from anyone who's moved past that.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>AI Is for DevOps: How a Neural Network Debugs Failed Pipelines</title>
      <dc:creator>Sergey Byvshev</dc:creator>
      <pubDate>Wed, 01 Apr 2026 14:38:51 +0000</pubDate>
      <link>https://dev.to/javdet/ai-is-for-devops-how-a-neural-network-debugs-failed-pipelines-h72</link>
      <guid>https://dev.to/javdet/ai-is-for-devops-how-a-neural-network-debugs-failed-pipelines-h72</guid>
      <description>&lt;p&gt;How often does someone rush to you wide-eyed, begging for help with a broken pipeline? Or you find yourself staring at a red status in Slack on a Friday evening, knowing the next 15–20 minutes will be spent on routine work: open the log, find the error line, compare with the last commit, check dependencies…&lt;/p&gt;

&lt;p&gt;The work is straightforward. And that's exactly why it's boring — a perfect candidate for automation.&lt;/p&gt;

&lt;p&gt;Fortunately, neural networks can now handle this for us and provide solid advice (not all of them, but some definitely can).&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Think Through Beforehand
&lt;/h2&gt;

&lt;p&gt;Before writing any code, it's worth answering four questions. They'll define the architecture of the entire solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What events trigger the analysis?&lt;/strong&gt; In our case — a job that finished unsuccessfully in CI/CD. To start diagnostics, it's enough to pass the agent the last 50 lines of the build log and the pipeline file contents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What data sources will be needed?&lt;/strong&gt; The main ones are the version control system (repository access), CI/CD (full log, related jobs), an endpoint availability checker, and CI agent resource consumption metrics.&lt;/p&gt;

&lt;p&gt;To build such an engineer, you first need to determine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What events and in what format to provide to the engineer?&lt;/li&gt;
&lt;li&gt;What sources and data might be needed?&lt;/li&gt;
&lt;li&gt;How to manipulate this data to identify the root cause?&lt;/li&gt;
&lt;li&gt;What should the diagnostic output look like in form and content?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to analyze the data?&lt;/strong&gt; This is the most interesting part, because there are many scenarios depending on the job type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build jobs — dependency issues (missing, incorrectly specified, unavailable), code errors, insufficient build resources.&lt;/li&gt;
&lt;li&gt;Test jobs — code errors, incorrectly written tests.&lt;/li&gt;
&lt;li&gt;Deploy jobs — manifest errors, issues on the target platform side.&lt;/li&gt;
&lt;li&gt;Common problems — errors in the pipeline/workflow itself, missing utilities on the agent, agent initialization issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What should the report format be?&lt;/strong&gt; Most often, such a report is read by human eyes in a chat, so it should be written in plain language. Concise, facts only: what was found, most probable causes, specific steps to fix. A convenient place for such a report is a thread under the corresponding error message or a dedicated channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution Architecture
&lt;/h2&gt;

&lt;p&gt;At a high level, the flow works like this: an event arrives about a job completing unsuccessfully. Then we request data for analysis: build logs, pipeline description. Based on this data and its system prompt, the AI agent performs the failure analysis. During the process, the assistant can independently check the repository, see what changed, and so on. In case of external endpoint unavailability errors, it can verify this. On a failed deploy, it can check application logs and metrics. As a result, several most probable failure causes and remediation steps are generated. This data is then sent to the team chat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76eu1yjwb3uolirk8sga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76eu1yjwb3uolirk8sga.png" alt="Architecture" width="781" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Time to Implement
&lt;/h2&gt;

&lt;p&gt;Some implementation aspects are covered in more detail in a the article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://n8n.io/" rel="noopener noreferrer"&gt;n8n&lt;/a&gt; — You can quickly launch n8n with the MCP update using docker-compose (see below)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/" rel="noopener noreferrer"&gt;Gitlab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/oss/loki/" rel="noopener noreferrer"&gt;Loki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://slack.com/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Incoming Events
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnamijbm1k9f5drasnnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnamijbm1k9f5drasnnz.png" alt="Input chain" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step is to create a webhook in n8n. Gitlab uses the &lt;code&gt;X-Gitlab-Token&lt;/code&gt; header for authentication, so in n8n we select Header Auth and specify the corresponding credential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe72bpm3a5mvsidk2orc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe72bpm3a5mvsidk2orc2.png" alt="Webhook" width="800" height="1511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rkarpsyjf6xp5lovxgn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rkarpsyjf6xp5lovxgn.png" alt="Webhook Auth" width="800" height="464"&gt;&lt;/a&gt;&lt;br&gt;
In Gitlab, we configure webhook delivery. This can be done for an individual repository or for an entire group. We specify the webhook address and the secret token, and from the event types we select Pipeline events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdyqvazodc14hz1wia91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdyqvazodc14hz1wia91.png" alt="Gitlab webhook" width="800" height="632"&gt;&lt;/a&gt;&lt;br&gt;
Then, using the &lt;code&gt;If&lt;/code&gt; node, we filter out all non-failed events — we don't need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Collection
&lt;/h2&gt;

&lt;p&gt;As soon as we receive a job failure event, we request details from Gitlab. For this, you'll need to create a gitlab access token (I recommend read-only) and the corresponding credential in n8n.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fji0db0u4ritjd7hre4pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fji0db0u4ritjd7hre4pk.png" alt="Collection data" width="800" height="492"&gt;&lt;/a&gt;&lt;br&gt;
As soon as we receive a job failure event, we request details from Gitlab. For this, you'll need to create a gitlab access token (I recommend read-only) and the corresponding credential in n8n.&lt;br&gt;
Then we merge all the collected data using the Merge node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Analysis
&lt;/h2&gt;

&lt;p&gt;Data arrives to the agent in the following format:&lt;/p&gt;

&lt;p&gt;`[&lt;/p&gt;

&lt;p&gt;{ "job_log": "Last 50 lines of failed job" },&lt;/p&gt;

&lt;p&gt;{ "data": "Content .gitlab-ci.yml" },&lt;/p&gt;

&lt;p&gt;{ "pipeline": {} },&lt;/p&gt;

&lt;p&gt;{ "failed_job": {} }&lt;/p&gt;

&lt;p&gt;]`&lt;/p&gt;

&lt;p&gt;This format should be communicated to the agent in advance via the system prompt. There we also describe the available tools, the investigation strategy (based on the considerations outlined above), and the desired output report format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ot5k9cg3bm9vzd4hdn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ot5k9cg3bm9vzd4hdn3.png" alt="ai-asisstant" width="800" height="549"&gt;&lt;/a&gt;&lt;br&gt;
It's best to use the latest model versions, as they handle MCP tool use significantly better. We don't connect memory here, since each build failure is an independent event for the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Tools
&lt;/h2&gt;

&lt;p&gt;The agent has access to three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gitlab MCP — for retrieving additional information about the failed job, code changes, etc.&lt;/li&gt;
&lt;li&gt;Grafana MCP — for retrieving CI agent metrics, as well as failed deploy logs.&lt;/li&gt;
&lt;li&gt;HTTP Request — n8n's built-in tool for checking endpoint availability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: make sure your MCP servers are running in remote mode. If an MCP server doesn't support remote out of the box, you can solve this with mcpgateway — it proxies HTTP to stdin. For the transport method, streaming HTTP is the best choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Posting to Chat
&lt;/h2&gt;

&lt;p&gt;The final step is sending the generated report to Slack. The report goes to the selected channel or thread.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcu8mpigda60jxtk9w7g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcu8mpigda60jxtk9w7g.png" alt="Output" width="720" height="644"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and Real-World Examples
&lt;/h2&gt;

&lt;p&gt;The final workflow looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fag2bbghealcpt5vigua5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fag2bbghealcpt5vigua5.png" alt="full-workflow" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Failed Build
&lt;/h2&gt;

&lt;p&gt;Gradle can't resolve a dependency. The agent determines that this is a dependency resolution issue, not a compilation error. It provides specific causes: the artifact isn't published in the repository, or credentials are unavailable inside the Docker build context. For each cause — concrete steps to fix.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawrraja08mehvaosn86s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawrraja08mehvaosn86s.png" alt="Gitlab-logs" width="800" height="344"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivrjyesmjis90gj8mt5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivrjyesmjis90gj8mt5w.png" alt="Slack-message" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: Infrastructure Change Errors
&lt;/h2&gt;

&lt;p&gt;Terraform plan fails with Unsupported argument errors. The agent recognizes that the HCL configuration contains attributes not supported by the current DigitalOcean provider schema. It provides three probable causes — from the wrong resource type to provider version mismatch — with specific remediation steps for each.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8gpz8eucbk2a0pwoci1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8gpz8eucbk2a0pwoci1.png" alt="gitlab-example" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvs1f3cz89trpgp2sh5st.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvs1f3cz89trpgp2sh5st.png" alt="report-example" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We've built an assistant that performs full error analysis in approximately 30 seconds. This allows the team to respond to failed jobs significantly faster and spend their time on real engineering tasks rather than routine log analysis.&lt;/p&gt;

&lt;p&gt;Token consumption stays at the level of a few thousand per analysis.&lt;/p&gt;




&lt;p&gt;Base workflow version is &lt;a href="https://github.com/javdet/automagicops-workflows/tree/main/workflows/CI_CDAssistant" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
Full tutorial with all scripts can be seen &lt;a href="https://www.patreon.com/posts/how-neural-in-30-153563490" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ai</category>
      <category>infrastructure</category>
      <category>cicd</category>
    </item>
    <item>
      <title>AI Alert Assistant: How n8n + LLM Replace Routine Diagnostics</title>
      <dc:creator>Sergey Byvshev</dc:creator>
      <pubDate>Tue, 24 Mar 2026 06:35:05 +0000</pubDate>
      <link>https://dev.to/javdet/ai-alert-assistant-how-n8n-llm-replace-routine-diagnostics-a9b</link>
      <guid>https://dev.to/javdet/ai-alert-assistant-how-n8n-llm-replace-routine-diagnostics-a9b</guid>
      <description>&lt;p&gt;Anyone who has dealt with keeping services running knows how exhausting and unpredictably time-consuming incident diagnostics and resolution can be.&lt;/p&gt;

&lt;p&gt;Over the years, I've watched the evolution of incident response processes — from "whoever spots the problem first owns it" to strictly defined 24/7 on-call rotations, SLA-driven response times, runbook adherence, and separation of responsibility across platforms.&lt;/p&gt;

&lt;p&gt;One thing has remained constant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gathering data from multiple sources
1.1. Metrics
1.2. Logs
1.3. Traces
1.4. Release and maintenance timelines&lt;/li&gt;
&lt;li&gt;Analysis based on personal knowledge and experience&lt;/li&gt;
&lt;li&gt;Formulating possible solutions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have a documented procedure for every situation, that simplifies things somewhat — but it doesn't teach the investigative mindset needed for real troubleshooting.&lt;/p&gt;

&lt;p&gt;Writing and maintaining a &lt;a href="https://runbooks.prometheus-operator.dev/" rel="noopener noreferrer"&gt;runbook&lt;/a&gt; for every alert is tedious work, which is exactly why an experienced engineer will always outperform a library of hundreds of runbooks.&lt;/p&gt;

&lt;p&gt;But what if an engineer's function could be performed even when no engineer is physically present?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6yyrj5u5xrhsza6ujf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6yyrj5u5xrhsza6ujf6.png" alt=" " width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Designing the Assistant: What to Define Upfront
&lt;/h1&gt;

&lt;p&gt;Before writing any code, four questions need to be answered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What events, and in what format, should be provided to the agent?&lt;/li&gt;
&lt;li&gt;What data sources might it need?&lt;/li&gt;
&lt;li&gt;How should it manipulate that data to identify the root cause?&lt;/li&gt;
&lt;li&gt;What should the diagnostic report look like in terms of form and content?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break down each one. A link to the workflow itself can be found below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Events and Format
&lt;/h2&gt;

&lt;p&gt;Typically, what's sufficient to kick off diagnostics is an event containing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Alertname&lt;br&gt;
Description&lt;br&gt;
Labels&lt;br&gt;
job_name&lt;br&gt;
namespac&lt;br&gt;
pod&lt;br&gt;
env&lt;br&gt;
region&lt;br&gt;
Grafana Dashboard&lt;br&gt;
Runbook Url&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Data Sources
&lt;/h2&gt;

&lt;p&gt;Most frequently, we turn to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metrics that have breached acceptable thresholds&lt;/li&gt;
&lt;li&gt;Resource consumption and load metrics&lt;/li&gt;
&lt;li&gt;Error logs&lt;/li&gt;
&lt;li&gt;The platform — Kubernetes or a standalone server&lt;/li&gt;
&lt;li&gt;Related CI/CD releases&lt;/li&gt;
&lt;li&gt;Alert definitions and firing conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data Analysis
&lt;/h2&gt;

&lt;p&gt;There is arguably no canonical sequence of steps for analysis. The diagnostic process is inherently variable — which is why no one has yet managed to write a single script that covers every possible scenario. But we'll give it a shot.&lt;/p&gt;

&lt;p&gt;First, let's consider how we ourselves approach incident diagnosis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Examine what's happening with the metric that triggered the alert: determine the nature of the anomaly — a spike, monotonic growth, or a persistently critical value&lt;/li&gt;
&lt;li&gt;Determine whether this is a software-level failure or caused by issues at a lower layer&lt;/li&gt;
&lt;li&gt;Check infrastructure metrics: resources, networking, system limits&lt;/li&gt;
&lt;li&gt;Inspect logs at the point where the problem is occurring&lt;/li&gt;
&lt;li&gt;Determine how recently the affected components were updated and what changed&lt;/li&gt;
&lt;li&gt;Attempt to interact with the components directly — through the orchestrator or a Linux shell&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Report Format
&lt;/h2&gt;

&lt;p&gt;In most cases, this kind of report is meant to be read by humans, so it should be written in plain, natural language. Concise — just the discovered facts, a list of hypotheses, and possible remediation steps. The most convenient place for such a report is a thread under the corresponding alert in the team chat.&lt;/p&gt;

&lt;p&gt;Solution Architecture&lt;br&gt;
Here's the desired flow: when an alert fires, the event is sent to a webhook that extracts the relevant data and assembles a clear, well-structured prompt for the AI agent.&lt;/p&gt;

&lt;p&gt;The AI agent, guided by its system prompt and the available &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; tools, performs diagnostics and generates a report in a predefined format.&lt;/p&gt;

&lt;p&gt;The report is then posted to the team chat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcczx1ggz3w9newtmwmc9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcczx1ggz3w9newtmwmc9.png" alt=" " width="691" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Implementation
&lt;/h1&gt;

&lt;p&gt;If you're in a hurry, you can view the finished workflow below. &lt;/p&gt;

&lt;p&gt;As the execution environment for workflows like this, I chose n8n because it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lets you build easily readable automations fairly quickly&lt;/li&gt;
&lt;li&gt;Makes it simple to share your work&lt;/li&gt;
&lt;li&gt;Separates logic from secrets and other hardcoded values&lt;/li&gt;
&lt;li&gt;Has a free self-hosted version&lt;/li&gt;
&lt;li&gt;Has an enormous community&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Personally, it reminds me of &lt;a href="https://www.jenkins.io/" rel="noopener noreferrer"&gt;Jenkins&lt;/a&gt; about ten years ago — and Jenkins was great.&lt;br&gt;
You can install n8n using any of the methods described in the documentation, for example using &lt;a href="https://docs.n8n.io/hosting/installation/server-setups/docker-compose/" rel="noopener noreferrer"&gt;docker-compose&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, the implementation will depend on the systems you use. In my case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; + &lt;a href="https://github.com/prometheus/alertmanager" rel="noopener noreferrer"&gt;Alertmanager&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/oss/loki/" rel="noopener noreferrer"&gt;Grafana Loki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://slack.com/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preprocessing Incoming Events
&lt;/h2&gt;

&lt;p&gt;Alertmanager can send alerts to a custom webhook. In n8n, all you need to do is create a Webhook trigger node and and you can also specify authentication parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7i9h86evv27zjif4b9e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7i9h86evv27zjif4b9e.png" alt=" " width="800" height="1486"&gt;&lt;/a&gt;&lt;br&gt;
Add data about the created webhook to the new receiver n8n in alertmanager.&lt;br&gt;
After this, we'll be able to send alerts from Alertmanager to our workflow. However, the received messages contain unnecessary data, and the format is not entirely appropriate. This will make it difficult for LLM to understand what's being asked of it, leading to increased token consumption. Therefore, we'll make a small modification using Code node.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejvgyp0a56oac6tors37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejvgyp0a56oac6tors37.png" alt=" " width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll likely want to store certain values as variables — for instance, the UID of your Prometheus datasource in Grafana.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qquaey1xourwhmfig24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qquaey1xourwhmfig24.png" alt=" " width="800" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Agent
&lt;/h2&gt;

&lt;p&gt;A prerequisite for the AI ​​Agent node to operate is a connected LLM. Almost any neural network can be connected, but in my experience, Codex and Opus perform best.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq6wcclbpob7abjpku2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq6wcclbpob7abjpku2a.png" alt=" " width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don't use Memory here, since each alert is an independent event unrelated to others.&lt;br&gt;
One of the key aspects is writing the system prompt. What should it include?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent purpose — what it's supposed to do&lt;/li&gt;
&lt;li&gt;Brief description of your infrastructure and the type of service you provide&lt;/li&gt;
&lt;li&gt;Description of each MCP tool — e.g., use the Kubernetes MCP to get pod status, related events, etc.&lt;/li&gt;
&lt;li&gt;Important rules to follow and pitfalls to avoid — e.g., never ask questions, write the response in a specific language, never make any changes to the infrastructure&lt;/li&gt;
&lt;li&gt;Diagnostic guidelines — essentially what we discussed in the Data Analysis section above&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  MCP — The Agent's Eyes and Ears
&lt;/h2&gt;

&lt;p&gt;MCP tools serve as the agent's eyes and ears, giving it the ability to interact with the subject of diagnosis. The specific list may vary depending on your infrastructure, but the core categories of data sources (which we outlined earlier) remain the same. In my case, the list looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metrics — &lt;a href="https://github.com/grafana/mcp-grafana" rel="noopener noreferrer"&gt;mcp-grafana&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Logs — &lt;a href="https://github.com/grafana/mcp-grafana" rel="noopener noreferrer"&gt;mcp-grafana&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Platform — &lt;a href="https://github.com/containers/kubernetes-mcp-server" rel="noopener noreferrer"&gt;kubernetes-mcp&lt;/a&gt;, &lt;a href="https://github.com/digitalocean/digitalocean-mcp" rel="noopener noreferrer"&gt;digitalocean-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CI/CD releases — &lt;a href="https://github.com/zereight/gitlab-mcp" rel="noopener noreferrer"&gt;gitlab-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Alert descriptions — vector store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48zj1bjwnlzolmoane7y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48zj1bjwnlzolmoane7y.png" alt=" " width="800" height="433"&gt;&lt;/a&gt;&lt;br&gt;
When running your mcp's, make sure they are running in remote http streaming mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vector Store
&lt;/h2&gt;

&lt;p&gt;The knowledge base deserves separate attention. It allows you to store large volumes of information and perform fast lookups. This saves tokens and reduces the time spent on external system queries. I use Qdrant as this knowledge base. I strongly recommend setting a service API token for authentication.&lt;/p&gt;

&lt;p&gt;Next, you need to create a collection where your knowledge will be stored. You can do this through the web interface at http://:6333/dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqamyycaucetlgylnqxm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqamyycaucetlgylnqxm.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;
Create a QdrantApi account and use it to connect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu444rl04e9i9e4hyv407.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu444rl04e9i9e4hyv407.png" alt=" " width="800" height="461"&gt;&lt;/a&gt;&lt;br&gt;
Once the database is connected to the agent as a tool, it's time to load it with knowledge. I use a separate workflow for this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjqq71gbwc56ivwpxpsh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjqq71gbwc56ivwpxpsh.png" alt=" " width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply run this workflow and upload your knowledge file(s) through the form that appears — they'll be saved to the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Posting to Chat
&lt;/h2&gt;

&lt;p&gt;After the AI agent completes its work, we need to send the results to the chat where engineers will see them. The delivery chain consists of three nodes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search for recent messages in the alerts channel. Unfortunately, not all group chats support keyword search via API, so the last 10 messages are retrieved instead.&lt;/li&gt;
&lt;li&gt;Find the message that corresponds to our alert.&lt;/li&gt;
&lt;li&gt;Post the diagnostic results as a thread reply to that message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Slack integration, you'll need to set up authentication following the official &lt;a href="https://docs.n8n.io/integrations/builtin/credentials/slack/?utm_source=n8n_app&amp;amp;utm_medium=credential_settings&amp;amp;utm_campaign=create_new_credentials_modal#slack-trigger-configuration" rel="noopener noreferrer"&gt;Slack API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51mnmg8nmgxjdgdfyee1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51mnmg8nmgxjdgdfyee1.png" alt=" " width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing and Examples
&lt;/h1&gt;

&lt;p&gt;Here's what the final workflow looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpj44qptf9lgz4zi6w9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frpj44qptf9lgz4zi6w9m.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;br&gt;
I've tested minor variations of this workflow across several projects, and here are the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehzl4movw132nvuo0x4f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fehzl4movw132nvuo0x4f.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl569fg5ms49qh2w9xr9g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl569fg5ms49qh2w9xr9g.png" alt=" " width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On average, analyzing an alert takes 30 seconds. In that time, the agent manages to inspect metrics, review logs, assess the state of the K8s cluster, and deliver a verdict.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh4cokq5kxwm1ubp6gtv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh4cokq5kxwm1ubp6gtv.png" alt=" " width="800" height="737"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvnnk1snyymtspeot21i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvnnk1snyymtspeot21i.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;What we end up with is an assistant that gets to work the instant an alert fires. The analysis time is minimal, which guarantees that by the time an engineer sees the alert, the initial diagnostics will have already been completed.&lt;/p&gt;

&lt;p&gt;This is just one of the directions where AI can meaningfully simplify life for infrastructure teams — and for development teams who are forced to handle their own support. The agent doesn't replace the engineer, but it takes on the first-response diagnostics and shortens the gap between "alert fired" and "we understand what's going on." And at night — when the on-call engineer is asleep — that can be invaluable.&lt;/p&gt;




&lt;p&gt;There is base version of workflow: &lt;a href="https://github.com/javdet/automagicops-workflows/tree/main/workflows/AlertAssistant" rel="noopener noreferrer"&gt;https://github.com/javdet/automagicops-workflows/tree/main/workflows/AlertAssistant&lt;/a&gt;&lt;br&gt;
Want to quickly implement a similar flow for yourself? Read the full &lt;a href="https://www.patreon.com/posts/153231436" rel="noopener noreferrer"&gt;Patreon&lt;/a&gt; guide with detailed examples and practical tips.&lt;br&gt;
Author of the article: &lt;a href="https://linkedin.com/in/sergeybyvshev" rel="noopener noreferrer"&gt;https://linkedin.com/in/sergeybyvshev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sre</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
