<?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: K@zuki.</title>
    <description>The latest articles on DEV Community by K@zuki. (@corrupt952).</description>
    <link>https://dev.to/corrupt952</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%2F959720%2F3f7e6cac-308d-48f8-8b4f-3eb97cf30a77.jpeg</url>
      <title>DEV Community: K@zuki.</title>
      <link>https://dev.to/corrupt952</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/corrupt952"/>
    <language>en</language>
    <item>
      <title>How I Built E2E Tests for Chrome Extensions Using Playwright and CDP</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Sun, 13 Jul 2025 10:15:32 +0000</pubDate>
      <link>https://dev.to/corrupt952/how-i-built-e2e-tests-for-chrome-extensions-using-playwright-and-cdp-11fl</link>
      <guid>https://dev.to/corrupt952/how-i-built-e2e-tests-for-chrome-extensions-using-playwright-and-cdp-11fl</guid>
      <description>&lt;h1&gt;
  
  
  End-to-End Testing for Chrome Extensions with Playwright
&lt;/h1&gt;

&lt;p&gt;The rainy days continue, but it's the perfect season for indoor coding, isn't it?&lt;br&gt;
I'm &lt;a href="https://twitter.com/corrupt952" rel="noopener noreferrer"&gt;K@zuki.&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How do you test your Chrome extensions?&lt;/p&gt;

&lt;p&gt;To be honest, when it comes to E2E testing for Chrome extensions, you might think: "Is it even possible?", "Is it necessary?", "Sounds complicated..."&lt;br&gt;
I thought the same at first.&lt;/p&gt;

&lt;p&gt;But when I actually tried it, it turned out to be surprisingly doable.&lt;br&gt;
Moreover, by combining Playwright with Chrome DevTools Protocol (CDP), I found that you can write quite practical tests.&lt;/p&gt;

&lt;p&gt;Today, I'd like to share the E2E testing approach I implemented for my Chrome extension called &lt;a href="https://github.com/corrupt952/snack_time" rel="noopener noreferrer"&gt;Snack Time&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/corrupt952" rel="noopener noreferrer"&gt;
        corrupt952
      &lt;/a&gt; / &lt;a href="https://github.com/corrupt952/SnackTime" rel="noopener noreferrer"&gt;
        SnackTime
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Snack Time&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;The repository for chrome extension to remind you to take a break and have a snack.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Install this extension from the &lt;a href="https://chromewebstore.google.com/detail/snack-time/okaijbdacnkekgchlligfkjccijcghfn" rel="nofollow noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Development&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Prerequisites&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://asdf-vm.com/" rel="nofollow noopener noreferrer"&gt;asdf&lt;/a&gt; or compatible .tool-versions file&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Setup&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Node.js&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;asdf install&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install pnpm&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install -g pnpm&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Debugging&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;This extension is developed using CRXJS.
So, if you run &lt;code&gt;pnpm dev&lt;/code&gt; and load the output directory as an extension, the file will be updated in real time.
&lt;code&gt;OUTPUT_DIR&lt;/code&gt; is the directory where the output will be placed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When modifying &lt;code&gt;Content.tsx&lt;/code&gt; (Timer component), hot reload may not work properly. In this case, you need to restart the extension:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;chrome://extensions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find "Snack Time" extension&lt;/li&gt;
&lt;li&gt;Click the reload button (↻) or toggle the extension off and on&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a known limitation of Chrome Extension's content scripts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install dependencies&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pnpm install&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the development server&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;OUTPUT_DIR=&lt;span class="pl-k"&gt;~&lt;/span&gt;/Documents/snack-time pnpm dev&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Load the extension…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/SnackTime" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You can write E2E tests for Chrome extensions using Playwright&lt;/li&gt;
&lt;li&gt;Everything, including popups, can be accessed as web pages&lt;/li&gt;
&lt;li&gt;Integration tests between popups and content scripts are achievable with CDP&lt;/li&gt;
&lt;li&gt;Page Object Pattern makes test code maintainable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bringToFront()&lt;/code&gt; enables smooth switching between multiple windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why E2E Testing for Chrome Extensions is Challenging
&lt;/h2&gt;

&lt;p&gt;First, let's organize the unique challenges of Chrome extensions.&lt;br&gt;
Compared to regular web applications, Chrome extensions have several special circumstances.&lt;/p&gt;
&lt;h3&gt;
  
  
  Multiple Execution Contexts
&lt;/h3&gt;

&lt;p&gt;Chrome extensions actually run in multiple "worlds":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Popup&lt;/strong&gt; - The screen that appears when you click the extension icon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Scripts&lt;/strong&gt; - Scripts injected into each web page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Options Page&lt;/strong&gt; - Settings screen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background&lt;/strong&gt; - Scripts running in the background (Service Worker)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other custom pages&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since these work together, it seems difficult to test with simple E2E tests.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Peculiarity of Popups
&lt;/h3&gt;

&lt;p&gt;One of the challenges when wanting to perform E2E testing for Chrome extensions is "not knowing how to interact with popups."&lt;br&gt;
Many Chrome extensions require users to click the extension button in the toolbar and then click elements within the popup to trigger events.&lt;br&gt;
However, Playwright and similar tools cannot normally access this toolbar, which makes it seem difficult.&lt;/p&gt;
&lt;h3&gt;
  
  
  Extension-Specific URLs
&lt;/h3&gt;

&lt;p&gt;Chrome extension pages have special URLs like &lt;code&gt;chrome-extension://[extension-id]/popup.html&lt;/code&gt;.&lt;br&gt;
This extension ID changes depending on the environment, so you can't hardcode it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solving with Playwright
&lt;/h2&gt;

&lt;p&gt;Now, let's get to the main topic.&lt;br&gt;
Actually, Playwright can solve these problems quite elegantly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Any tool using the same driver should be capable of this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Basic Setup
&lt;/h2&gt;

&lt;p&gt;First, let's look at how to load a Chrome extension with Playwright.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// e2e/fixtures/extension.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BrowserContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathToExtension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchPersistentContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Extensions don't work in headless mode&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;`--disable-extensions-except=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pathToExtension&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`--load-extension=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pathToExtension&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Dynamically get the extension ID&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chrome://extensions/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cr-toggle#devMode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extensions-item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extensionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;extensionCard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By preparing this fixture and reusing it in each test, we can handle the issue of dynamically changing IDs.&lt;/p&gt;

&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;launchPersistentContext&lt;/code&gt; to load the extension&lt;/li&gt;
&lt;li&gt;Dynamically retrieve the extension ID from &lt;code&gt;chrome://extensions/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Define as a fixture so it can be reused across all tests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Opening Popups as Separate Pages
&lt;/h2&gt;

&lt;p&gt;Here's the crucial point: by opening the popup as a regular page, we can avoid the freeze issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Open the popup as a new page&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;popupPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;popupPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/popup/index.html`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Now you can test it like a normal page!&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;popupPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button:has-text("5:00")&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, it's different from actual user interaction, but it's sufficient for functional testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling Multi-Window with CDP
&lt;/h2&gt;

&lt;p&gt;Next, let's talk about switching between popups and content pages.&lt;/p&gt;

&lt;p&gt;In Snack Time, the timer specified in the popup is displayed within the content page.&lt;br&gt;
Even if you try to test such a mechanism, you can't test it by operating in the tab where the popup is open because the active tab is not the content page.&lt;/p&gt;

&lt;p&gt;This is where Chrome DevTools Protocol (CDP) comes in handy for finer browser control.&lt;br&gt;
&lt;code&gt;Page.bringToFront&lt;/code&gt; is particularly useful as it brings a specific page to the front, allowing you to switch the active tab.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// content-timer.page.ts - bringToFront implementation&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;bringToFront&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;newCDPSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page.bringToFront&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this mechanism, you can write tests like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Set timer from popup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Open the test target page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContentTimerPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Open the popup&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;popupPageHandle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;popupPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PopupPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;popupPageHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;popupPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Bring content page to front (using CDP)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contentPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bringToFront&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Set timer in popup&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;popupPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clickPresetButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Verify timer is displayed on content page&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contentPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contentPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyTimerVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The great thing is that we can test in a way that's close to actual user operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant Test as Test Code
    participant Popup as Popup Page&amp;lt;br/&amp;gt;(New Tab)
    participant CDP as Chrome DevTools Protocol
    participant Content as Content Page&amp;lt;br/&amp;gt;(example.com)
    participant Timer as Timer Element&amp;lt;br/&amp;gt;(#snack-time-root)

    Test-&amp;gt;&amp;gt;Content: 1. Open example.com
    Test-&amp;gt;&amp;gt;Popup: 2. Open popup in new tab&amp;lt;br/&amp;gt;chrome-extension://id/popup.html
    Test-&amp;gt;&amp;gt;CDP: 3. bringToFront()&amp;lt;br/&amp;gt;(Content Page)
    CDP-&amp;gt;&amp;gt;Content: 4. Make content page active
    Test-&amp;gt;&amp;gt;Popup: 5. Click preset button (5 min)
    Popup-&amp;gt;&amp;gt;Timer: 6. Inject timer
    Test-&amp;gt;&amp;gt;Timer: 7. waitForSelector("#snack-time-root")
    Test-&amp;gt;&amp;gt;Timer: 8. Verify timer display
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Organizing with Page Object Pattern
&lt;/h2&gt;

&lt;p&gt;As test code grows, maintenance becomes challenging.&lt;br&gt;
This is where the Page Object Pattern comes in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// popup.page.ts - Page Object implementation example&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PopupPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;presetButtonMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;25&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`chrome-extension://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/popup/index.html`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;clickPresetButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;25&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presetButtonMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`button:has-text("&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timeText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;")`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Define other actions similarly...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, test cases can be kept simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Test Scenarios
&lt;/h2&gt;

&lt;p&gt;Let me introduce some actual tests I've written.&lt;/p&gt;

&lt;h3&gt;
  
  
  Independence Test Across Multiple Tabs
&lt;/h3&gt;

&lt;p&gt;Chrome extensions need to work independently for each tab. We can test this too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Independent timers work in different tabs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;extensionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Set timer in tab 1&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... set timer ...&lt;/span&gt;

  &lt;span class="c1"&gt;// Set different timer in tab 2&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.google.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... set timer ...&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify both timers are working independently&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bringToFront&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contentPage1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyTimerVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bringToFront&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contentPage2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyTimerVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Drag &amp;amp; Drop Test
&lt;/h3&gt;

&lt;p&gt;You can also test user interactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;dragTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deltaX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timerRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;down&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deltaX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;up&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pitfalls and Solutions
&lt;/h2&gt;

&lt;p&gt;During implementation, I encountered several pitfalls.&lt;br&gt;
Honestly, these are unavoidable challenges.&lt;/p&gt;
&lt;h3&gt;
  
  
  Waiting for Asynchronous Operations
&lt;/h3&gt;

&lt;p&gt;Chrome extensions have many asynchronous operations, so you need to wait appropriately.&lt;br&gt;
This is fundamental in E2E testing, not just for Chrome extensions, but it's especially important here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wait for timer to appear&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;waitForTimer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#snack-time-root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Can't Use Headless Mode
&lt;/h3&gt;

&lt;p&gt;Chrome extensions don't work in headless mode. You need to set &lt;code&gt;headless: false&lt;/code&gt; even in CI.&lt;br&gt;
In CI services like GitHub Actions, you can handle this by using Xvfb.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and Cons of Implementation
&lt;/h2&gt;

&lt;p&gt;Here's what I've learned from actual maintenance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;Rating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test Feasibility&lt;/td&gt;
&lt;td&gt;Popup and content script integration&lt;/td&gt;
&lt;td&gt;◎ Achievable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Operation Reproduction&lt;/td&gt;
&lt;td&gt;Some differences from actual operations&lt;/td&gt;
&lt;td&gt;△ Compromises needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintainability&lt;/td&gt;
&lt;td&gt;Organized with Page Object Pattern&lt;/td&gt;
&lt;td&gt;◎ Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD Integration&lt;/td&gt;
&lt;td&gt;No headless, Xvfb required&lt;/td&gt;
&lt;td&gt;△ Additional setup needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Curve&lt;/td&gt;
&lt;td&gt;Need to understand CDP&lt;/td&gt;
&lt;td&gt;△ Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Overall, while not perfect, it's sufficiently practical.&lt;/p&gt;

&lt;p&gt;Especially with Snack Time using Closed Shadow DOM, operations are difficult.&lt;br&gt;
I'm exploring alternative approaches for this, and I'll write about it if successful.&lt;/p&gt;

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

&lt;p&gt;E2E testing for Chrome extensions is more practical than you might think, isn't it?&lt;br&gt;
Sure, it's not perfect, and there are differences from actual popup behavior.&lt;br&gt;
But being able to write tests that cover major functionality is significant.&lt;/p&gt;

&lt;p&gt;Especially being able to test complex behaviors involving multiple contexts and user operation flows provides peace of mind.&lt;/p&gt;

&lt;p&gt;When creating Chrome extensions, I encourage you to try E2E testing.&lt;br&gt;
It might feel tedious at first, but once you set it up, it's not that different from regular web apps.&lt;/p&gt;

&lt;p&gt;The Snack Time code is available on &lt;a href="https://github.com/corrupt952/snack_time" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, so feel free to reference it if you're interested.&lt;/p&gt;

&lt;p&gt;Happy Testing! 🎉&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>chromeextension</category>
      <category>chrome</category>
    </item>
    <item>
      <title>How I Simplified My macOS App's AI Integration by Adding a Python Bridge</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Wed, 18 Jun 2025 14:30:56 +0000</pubDate>
      <link>https://dev.to/corrupt952/how-i-simplified-my-macos-apps-ai-integration-by-adding-a-python-bridge-220o</link>
      <guid>https://dev.to/corrupt952/how-i-simplified-my-macos-apps-ai-integration-by-adding-a-python-bridge-220o</guid>
      <description>&lt;p&gt;Hello, I'm &lt;a class="mentioned-user" href="https://dev.to/corrupt952"&gt;@corrupt952&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;Previously, I shared how to implement an MCP server in a macOS menu bar application using HTTP/SSE.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/corrupt952/implementing-mcp-server-in-a-macos-menu-bar-application-24p0" class="crayons-story__hidden-navigation-link"&gt;Implementing MCP Server in a macOS Menu Bar Application&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/corrupt952" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F959720%2F3f7e6cac-308d-48f8-8b4f-3eb97cf30a77.jpeg" alt="corrupt952 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/corrupt952" class="crayons-story__secondary fw-medium m:hidden"&gt;
              K@zuki.
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                K@zuki.
                
              
              &lt;div id="story-author-preview-content-2581961" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/corrupt952" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F959720%2F3f7e6cac-308d-48f8-8b4f-3eb97cf30a77.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;K@zuki.&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/corrupt952/implementing-mcp-server-in-a-macos-menu-bar-application-24p0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 10 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/corrupt952/implementing-mcp-server-in-a-macos-menu-bar-application-24p0" id="article-link-2581961"&gt;
          Implementing MCP Server in a macOS Menu Bar Application
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/swift"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;swift&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/claudecode"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;claudecode&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/corrupt952/implementing-mcp-server-in-a-macos-menu-bar-application-24p0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Today, I'd like to share how that architecture has evolved into something more robust and maintainable using FastMCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replaced direct HTTP/SSE implementation with a FastMCP-based Python bridge&lt;/li&gt;
&lt;li&gt;Improved security with host validation and access controls&lt;/li&gt;
&lt;li&gt;Achieved better Claude Desktop integration using standard stdio communication&lt;/li&gt;
&lt;li&gt;Maintained the same user experience while gaining ecosystem compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Change What Works?
&lt;/h2&gt;

&lt;p&gt;When I first implemented MCP support in Chimr (my macOS calendar notification app), I went with a pure Swift HTTP/SSE server.&lt;br&gt;
It worked, but as I used it more, several issues became apparent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Non-standard Implementation&lt;/strong&gt;: My custom HTTP approach didn't align with how most MCP servers operate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance Burden&lt;/strong&gt;: Keeping up with MCP protocol changes meant updating Swift code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited Ecosystem Integration&lt;/strong&gt;: Couldn't easily leverage Python MCP tools and libraries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simply put, I was swimming against the current of the MCP ecosystem.&lt;/p&gt;
&lt;h2&gt;
  
  
  The New Architecture
&lt;/h2&gt;

&lt;p&gt;Here's how the system works now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude Desktop &amp;lt;--(stdio)--&amp;gt; chimr.py &amp;lt;--(HTTP)--&amp;gt; Swift App
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of Claude Desktop talking directly to my Swift HTTP server, it now communicates with a Python-based FastMCP server that acts as a bridge. This might seem like adding complexity, but it actually simplifies things significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the FastMCP Server
&lt;/h2&gt;

&lt;p&gt;The heart of the new system is &lt;code&gt;chimr.py&lt;/code&gt;. Here's the basic structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env uv run --script
# /// script
# dependencies = [
#     "mcp",
#     "aiohttp"
# ]
# ///
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Chimr - MCP server for Chimr calendar integration
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asynccontextmanager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AsyncIterator&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="c1"&gt;# Chimr server configuration
&lt;/span&gt;&lt;span class="n"&gt;CHIMR_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHIMR_HOST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CHIMR_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHIMR_PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChimrConnection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CHIMR_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CHIMR_PORT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Send JSON-RPC request to Chimr server&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;request_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jsonrpc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

        &lt;span class="c1"&gt;# ... send HTTP request to Swift app
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's beautiful about FastMCP is how it handles all the MCP protocol complexity. I just focus on defining tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@asynccontextmanager&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;server_lifespan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AsyncIterator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Server lifespan management&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chimr_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chimr_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Create MCP server with proper lifecycle management
&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chimr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chimr calendar integration through the Model Context Protocol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lifespan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server_lifespan&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_today_events&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get today&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s calendar events from Chimr&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chimr_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_today_events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ... process response
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The beauty is that FastMCP handles all the stdio communication, JSON-RPC parsing, and error handling. I just implement the tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swift Side: From Server to Proxy
&lt;/h2&gt;

&lt;p&gt;On the Swift side, the HTTP server remains but with a different purpose. Instead of being the primary MCP server, it's now an internal API that the Python bridge calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;mcpRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MCPRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;protocolHandler&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;MCPResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MCPError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32603&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Internal error"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mcpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nf"&gt;sendHTTPResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But here's where it gets interesting - I added security features that weren't in the original implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhanced Security
&lt;/h2&gt;

&lt;p&gt;One concern with running an HTTP server (even on localhost) is security. The new implementation adds several layers of protection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;isConnectionAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AppSettings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;

    &lt;span class="c1"&gt;// If external access is allowed, accept all connections&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mcpAllowExternalAccess&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if the remote endpoint is in the allowed hosts&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipv4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debugDescription&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mcpAllowedHosts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mcpAllowedHosts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ipv6&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;ipv6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipv6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debugDescription&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mcpAllowedHosts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mcpAllowedHosts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mcpAllowedHosts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;@unknown&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows users to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restrict connections to localhost only (default)&lt;/li&gt;
&lt;li&gt;Define specific allowed hosts&lt;/li&gt;
&lt;li&gt;Enable external access if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benefits of This Approach
&lt;/h2&gt;

&lt;p&gt;After running this architecture for a while, the benefits are clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Standard Compliance&lt;/strong&gt;: Claude Desktop sees a standard stdio-based MCP server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Updates&lt;/strong&gt;: Protocol changes only require updating the Python bridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better Testing&lt;/strong&gt;: Can test the Swift API independently from MCP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem Access&lt;/strong&gt;: Can leverage Python MCP tools and libraries&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Implementation Tips
&lt;/h2&gt;

&lt;p&gt;If you're considering a similar architecture for your macOS app, here are some lessons learned:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Handle Connection Lifecycle Properly
&lt;/h3&gt;

&lt;p&gt;FastMCP provides lifecycle hooks - use them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@asynccontextmanager&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;server_lifespan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AsyncIterator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="c1"&gt;# Setup resources
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setup_connections&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="c1"&gt;# Cleanup
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cleanup_connections&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Error Handling at the Bridge
&lt;/h3&gt;

&lt;p&gt;The Python bridge should handle errors gracefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chimr_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No result returned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Keep the Swift API Simple
&lt;/h3&gt;

&lt;p&gt;Your Swift HTTP server doesn't need all the MCP complexity anymore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MCPRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Just handle tool calls, not the full MCP protocol&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comparing Architectures
&lt;/h2&gt;

&lt;p&gt;Looking back at both implementations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;HTTP/SSE Direct&lt;/th&gt;
&lt;th&gt;FastMCP Bridge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;High in Swift&lt;/td&gt;
&lt;td&gt;Low in both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Difficult&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standards&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;MCP-compliant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Enhanced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Complex&lt;/td&gt;
&lt;td&gt;Modular&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This architecture opens up interesting possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Bridges&lt;/strong&gt;: Could add Node.js or Rust bridges for different use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote Deployment&lt;/strong&gt;: The Swift app could run on a different machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin System&lt;/strong&gt;: Other apps could integrate with Chimr through the same API&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Sometimes the best solution isn't the most direct one. By adding a Python bridge layer, I actually simplified the overall system while gaining standard compliance and better security.&lt;/p&gt;

&lt;p&gt;If you're building MCP support into your macOS app, consider whether a bridge architecture might work better than direct implementation. The initial setup might seem more complex, but the long-term benefits are worth it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you implemented MCP in a native application?&lt;br&gt;
What architecture did you choose? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ai</category>
      <category>mcp</category>
      <category>python</category>
    </item>
    <item>
      <title>Implementing MCP Server in a macOS Menu Bar Application</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Tue, 10 Jun 2025 12:03:41 +0000</pubDate>
      <link>https://dev.to/corrupt952/implementing-mcp-server-in-a-macos-menu-bar-application-24p0</link>
      <guid>https://dev.to/corrupt952/implementing-mcp-server-in-a-macos-menu-bar-application-24p0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article contains some sections partially generated by LLM&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello, I'm K@zuki, and I have some content stockpiled that I'd like to release at some point.&lt;/p&gt;

&lt;p&gt;This is my first post in a while, and it's about implementing an MCP Server in a custom macOS menu bar resident app I've been developing. But rather than explain everything upfront, it's better to just show you what I've built:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1932208018906829040-437" src="https://platform.twitter.com/embed/Tweet.html?id=1932208018906829040"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1932208018906829040-437');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1932208018906829040&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Originally, I had implemented functionality in the resident app that could replace arbitrary strings with fullscreen notifications. I've now made this available as an MCP Server, allowing it to be used as a notification system when work is completed in Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Implemented HTTP Server instead of CLI tool in the resident app&lt;/li&gt;
&lt;li&gt;There are some security risks that need to be considered and addressed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Integrate MCP Server into an Existing App
&lt;/h2&gt;

&lt;p&gt;Simply put, it was driven by curiosity and interest. While a typical MCP Server would just require writing a CLI tool, in this case, by integrating an SSE-based MCP Server into an existing app, I can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Control the app itself&lt;/li&gt;
&lt;li&gt;Change app settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These operations can be called from MCP Clients like Claude Code, Cursor, or Claude Desktop. Additionally, I can leverage specific parts of the app (like fullscreen notifications), which can be quite convenient in certain scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Approach
&lt;/h2&gt;

&lt;p&gt;There are several patterns for implementing MCP Server, but currently there are roughly two main types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI&lt;/li&gt;
&lt;li&gt;HTTP/SSE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generally, servers are implemented as CLI tools, and thinking about it now, implementing as CLI would probably be the safer choice. However, since setting up listening on the resident app side seemed cumbersome, I decided to quickly set up an HTTP Server instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The general mechanism works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call &lt;a href="https://github.com/geelen/mcp-remote" rel="noopener noreferrer"&gt;mcp-remote&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Send requests to the HTTP Server running in the resident app via mcp-remote&lt;/li&gt;
&lt;li&gt;The resident app performs some processing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The flow can be represented in the following sequence diagram:&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%2Fhyeayqxr99cwcj9rmadw.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%2Fhyeayqxr99cwcj9rmadw.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since Claude Code and Claude Desktop only support CLI, I'm using mcp-remote to communicate with the HTTP Server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;For auto-startup with SwiftUI, define it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@StateObject&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;mcpServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SimpleMCPServerWrapper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;SimpleMCPServerWrapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ObservableObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SimpleMCPServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;deinit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can make it work by defining the MCP Server as follows. As you can see from the code, this requires more work than initially expected, so it's good to rely on AI assistance as needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Foundation&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Network&lt;/span&gt;

&lt;span class="c1"&gt;// Simple MCP Server implementation example&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;SimpleMCPServer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWListener&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NWParameters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt;
        &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allowLocalEndpointReuse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;NWListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWEndpoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;integerLiteral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create listener"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;

        &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newConnectionHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MCP Server started on port &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MCP Server stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Read HTTP request&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minimumIncompleteLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;maximumLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;65536&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isComplete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received request:&lt;/span&gt;&lt;span class="se"&gt;\n\(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;// Handle SSE connection&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /sse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleSSEConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle JSON-RPC request&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleJSONRPCRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isComplete&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleSSEConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Send SSE headers&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
        HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
        Content-Type: text/event-stream&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
        Cache-Control: no-cache&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
        Connection: keep-alive&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
        Access-Control-Allow-Origin: *&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
        &lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;

        """&lt;/span&gt;

        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contentProcessed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSE connection established"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c1"&gt;// Send initial connection event&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;connectEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"event: connected&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;data: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;connected&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;connectEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contentProcessed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleJSONRPCRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWConnection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Extract HTTP body (simplified implementation)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;requestString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requestData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
           &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bodyStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bodyData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestString&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bodyStart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upperBound&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;// Parse JSON-RPC request&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;JSONSerialization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bodyData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
               &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

                &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"initialize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s"&gt;"protocolVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2024-11-05"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s"&gt;"serverInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"simple-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1.0.0"&lt;/span&gt;
                            &lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                &lt;span class="s"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[:],&lt;/span&gt;
                                &lt;span class="s"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;
                            &lt;span class="p"&gt;]&lt;/span&gt;
                        &lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;

                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"tools/list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
                                &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Returns Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="s"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                    &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;
                                &lt;span class="p"&gt;]&lt;/span&gt;
                            &lt;span class="p"&gt;]]&lt;/span&gt;
                        &lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;

                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                       &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;toolName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;toolName&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"hello"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
                                    &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello, World! from MCP Server"&lt;/span&gt;
                                &lt;span class="p"&gt;]]&lt;/span&gt;
                            &lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
                        &lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                &lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Method not found"&lt;/span&gt;
                            &lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
                        &lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Method not found"&lt;/span&gt;
                        &lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;// Send response&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;responseData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;JSONSerialization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;withJSONObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;responseString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;httpResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
                    HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
                    Content-Type: application/json&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
                    Content-Length: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;responseData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="se"&gt;)\r&lt;/span&gt;&lt;span class="s"&gt;
                    Access-Control-Allow-Origin: *&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
                    &lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s"&gt;
                    &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;responseString&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;
                    """&lt;/span&gt;

                    &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;httpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contentProcessed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Calling from Claude Code
&lt;/h2&gt;

&lt;p&gt;To call from Claude Code, configure it as follows:&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;"hello-world"&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="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&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://localhost:8080/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;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;Once properly configured, you can call it and use the functionality prepared in the app, as shown in the introduction:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1932208018906829040-75" src="https://platform.twitter.com/embed/Tweet.html?id=1932208018906829040"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1932208018906829040-75');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1932208018906829040&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;Publishing as an HTTP Server means there are certain security risks that need attention.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Localhost Binding
&lt;/h3&gt;

&lt;p&gt;By default, it binds to localhost (127.0.0.1), but accidentally binding to 0.0.0.0 would make it accessible from external sources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Safe: localhost only&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;NWListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWEndpoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;integerLiteral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Dangerous: all interfaces&lt;/span&gt;
&lt;span class="c1"&gt;// let listener = try? NWListener(using: parameters, on: NWEndpoint.Port(rawValue: port)!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Lack of Authentication
&lt;/h3&gt;

&lt;p&gt;The current implementation has no authentication functionality, meaning any local process can access the MCP server. Consider implementing authentication as needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API key-based authentication&lt;/li&gt;
&lt;li&gt;Token-based authentication&lt;/li&gt;
&lt;li&gt;Connection source process verification&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. CORS Settings
&lt;/h3&gt;

&lt;p&gt;While not a concern in this case, CORS may need consideration in some scenarios. &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; is convenient for development, but in production environments, you should only allow specific origins rather than leaving it wide open.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Input Validation
&lt;/h3&gt;

&lt;p&gt;Properly validate JSON-RPC request input values to prevent injection attacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Resource Limits
&lt;/h3&gt;

&lt;p&gt;Depending on the app and PC, high load may be a concern, so pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Concurrent connection limits&lt;/li&gt;
&lt;li&gt;Request size limits&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This post covered integrating MCP Server into a macOS resident app. I'll probably switch to CLI by the time I release it, but please use this as an example for cases where you want to prepare an MCP Server interface for existing resident apps.&lt;/p&gt;

&lt;p&gt;By implementing it this way, you can relatively easily turn your own app into an MCP Server. You can also aim for easier configuration changes, utilize features that currently don't reach those itchy spots, and control specific app functions, making it very convenient.&lt;/p&gt;

&lt;p&gt;The app with integrated MCP Server is planned to recruit beta testers over the weekend, so if you're interested, keep an eye on Twitter or the blog.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ai</category>
      <category>claudecode</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Subtle but Important: IPAddr Behavior Change in Ruby 3.1</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Tue, 10 Sep 2024 14:05:02 +0000</pubDate>
      <link>https://dev.to/corrupt952/subtle-but-important-ipaddr-behavior-change-in-ruby-31-207</link>
      <guid>https://dev.to/corrupt952/subtle-but-important-ipaddr-behavior-change-in-ruby-31-207</guid>
      <description>&lt;p&gt;Have you ever encountered a situation where your Ruby code works perfectly fine in one version but behaves differently in another?&lt;br&gt;
Today, let's dive into a subtle yet important change in Ruby's &lt;code&gt;IPAddr&lt;/code&gt; class that occurred in version 3.1.&lt;/p&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The behavior of &lt;code&gt;IPAddr&lt;/code&gt; changed in Ruby 3.1.&lt;/li&gt;
&lt;li&gt;IPv4-mapped IPv6 addresses (e.g., &lt;code&gt;::ffff:127.0.0.1&lt;/code&gt;) are no longer considered equivalent to their IPv4 counterparts when compared to IPv4 ranges.&lt;/li&gt;
&lt;li&gt;This change can affect code that deals with IP address ranges, particularly in network configurations or access control lists.&lt;/li&gt;
&lt;li&gt;A related issue affecting Rails' &lt;code&gt;TRUSTED_PROXIES&lt;/code&gt; behavior has been reported: &lt;a href="https://github.com/rails/rails/issues/52862" rel="noopener noreferrer"&gt;Rails Issue #52862&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Change
&lt;/h2&gt;

&lt;p&gt;In Ruby versions prior to 3.1, when comparing an IPv4 range with an IPv4-mapped IPv6 address, the comparison would return &lt;code&gt;true&lt;/code&gt; if the IPv4 part of the address was within the range.&lt;br&gt;
However, this behavior changed in Ruby 3.1.&lt;/p&gt;

&lt;p&gt;Let's look at a concrete example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'ipaddr'&lt;/span&gt;

&lt;span class="n"&gt;ipv4_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;IPAddr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.0/8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ipv4_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;
&lt;span class="n"&gt;ipv4_mapped_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'::ffff:127.0.0.1'&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Ruby version: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;RUBY_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"IPv4 Range === IPv4 Address: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ipv4_range&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;ipv4_address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"IPv4 Range === IPv4-mapped Address: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ipv4_range&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;ipv4_mapped_address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this code in Ruby 3.0 gives us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# IPv4 Range === IPv4 Address
&amp;gt; IPAddr.new('127.0.0.0/8') === '127.0.0.1'
true

# IPv4 Range === IPv4-mapped Address:
&amp;gt; IPAddr.new('127.0.0.0/8') === '::ffff:127.0.0.1'
true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in Ruby 3.1 or later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# IPv4 Range === IPv4 Address
&amp;gt; IPAddr.new('127.0.0.0/8') === '127.0.0.1'
true

# IPv4 Range === IPv4-mapped Address:
&amp;gt; IPAddr.new('127.0.0.0/8') === '::ffff:127.0.0.1'
false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Did This Change?
&lt;/h2&gt;

&lt;p&gt;This change was introduced in the &lt;code&gt;ipaddr&lt;/code&gt; gem, which is a bundled gem in Ruby.&lt;br&gt;
The specific commit that introduced this change can be found &lt;a href="https://github.com/ruby/ipaddr/commit/da22ef8e6c886197f50281f29d61a30e27c0c88e" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implications
&lt;/h2&gt;

&lt;p&gt;While this change makes the behavior more technically correct, it can lead to unexpected results in existing code that relies on the previous behavior.&lt;br&gt;
This is particularly relevant for applications that deal with IP address ranges, such as those handling network configurations or access control lists.&lt;/p&gt;

&lt;p&gt;For instance, if you're using &lt;code&gt;IPAddr&lt;/code&gt; to check if an IP is within a trusted range, you might now need to explicitly handle IPv4-mapped addresses separately.&lt;/p&gt;

&lt;p&gt;FYI: &lt;a href="https://github.com/rails/rails/issues/52862" rel="noopener noreferrer"&gt;https://github.com/rails/rails/issues/52862&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Handle This in Your Rails Code
&lt;/h2&gt;

&lt;p&gt;If you're using Rails and configuring trusted proxies, you might need to adjust your configuration to account for this change.&lt;br&gt;
Here's how you can handle both IPv4 and IPv4-mapped IPv6 addresses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_dispatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trusted_proxies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RemoteIp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TRUSTED_PROXIES&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;# IPv4-mapped IPv6 loopback&lt;/span&gt;
  &lt;span class="no"&gt;IPAddr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'::ffff:127.0.0.0/104'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;# IPv4-mapped IPv6 private network&lt;/span&gt;
  &lt;span class="no"&gt;IPAddr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'::ffff:10.0.0.0/104'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;# other trusted proxies...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By explicitly including both IPv4 and IPv4-mapped IPv6 versions of your trusted proxy ranges, you ensure that your Rails application will correctly identify trusted proxies regardless of how they're represented.&lt;/p&gt;

&lt;p&gt;Remember to test your configuration thoroughly, especially if you're upgrading from an older version of Ruby or Rails.&lt;/p&gt;

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

&lt;p&gt;This change in &lt;code&gt;IPAddr&lt;/code&gt; behavior serves as a reminder of the importance of thorough testing across different Ruby versions, especially when dealing with core functionalities like network addressing.&lt;br&gt;
It also highlights the ongoing efforts in the Ruby community to improve consistency and correctness, even if it sometimes means breaking changes in minor version updates.&lt;/p&gt;

&lt;p&gt;Have you encountered any surprising behavior changes in your Ruby upgrades?&lt;br&gt;
How do you usually handle such situations?&lt;br&gt;
Share your experiences in the comments!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Introducing a Custom GitHub Action for Command Retry with Output Capture</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Mon, 19 Aug 2024 03:01:59 +0000</pubDate>
      <link>https://dev.to/corrupt952/introducing-a-custom-github-action-for-command-retry-with-output-capture-19o7</link>
      <guid>https://dev.to/corrupt952/introducing-a-custom-github-action-for-command-retry-with-output-capture-19o7</guid>
      <description>&lt;p&gt;I've published a GitHub Action that I've been using in my work.&lt;br&gt;
It's designed to retry specific commands, which sets it apart from similar actions available in the Marketplace.&lt;br&gt;
The key feature of this action is its ability to capture the command output, which is often a limitation in existing retry actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/retry-command" rel="noopener noreferrer"&gt;Marketplace&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/corrupt952" rel="noopener noreferrer"&gt;
        corrupt952
      &lt;/a&gt; / &lt;a href="https://github.com/corrupt952/actions-retry-command" rel="noopener noreferrer"&gt;
        actions-retry-command
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;retry-command&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Retries an Action step on failure.&lt;br&gt;
This action is unique compared to other actions in that it is possible to obtain the results of retries.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/retry-command" rel="noopener noreferrer"&gt;Marketplace&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Inputs&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;code&gt;command&lt;/code&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The command to run.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;code&gt;working_directory&lt;/code&gt;&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The directory in which to execute the command.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;code&gt;max_attempts&lt;/code&gt;&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The maximum number of times to attempt the command.&lt;br&gt;
Default is 5.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;code&gt;retry_interval&lt;/code&gt;&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The time to wait between retry attempts, in seconds. Default is 5.&lt;br&gt;
You can also write &lt;code&gt;$((RANDOM % 31))&lt;/code&gt; to make it a random value.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Output&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;code&gt;exit_code&lt;/code&gt;&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Exit code of the last command executed&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;code&gt;result&lt;/code&gt;&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Output of the last command executed&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Example&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Simple&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;- &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;corrupt952/actions-retry-command@v1&lt;/span&gt;
  &lt;span class="pl-ent"&gt;with&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;command&lt;/span&gt;: &lt;span class="pl-s"&gt;terraform plan -no-color&lt;/span&gt;
    &lt;span class="pl-ent"&gt;max_attempts&lt;/span&gt;: &lt;span class="pl-c1"&gt;3&lt;/span&gt;
    &lt;span class="pl-ent"&gt;retry_interval&lt;/span&gt;: &lt;span class="pl-c1"&gt;10&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Retry interval to a random time&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;- &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;corrupt952/actions-retry-command@v1&lt;/span&gt;
  &lt;span class="pl-ent"&gt;with&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;command&lt;/span&gt;: &lt;span class="pl-s"&gt;terraform plan -no-color&lt;/span&gt;
    &lt;span class="pl-ent"&gt;retry_interval&lt;/span&gt;: &lt;span class="pl-s"&gt;$((RANDOM % 31))&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Set working directory&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;- &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;corrupt952/actions-retry-command@v1&lt;/span&gt;
  &lt;span class="pl-ent"&gt;with&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/actions-retry-command" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How to Use
&lt;/h2&gt;

&lt;p&gt;The usage is straightforward.&lt;br&gt;
For example, if you want to retry terraform plan, you can use it like this:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;corrupt952/actions-retry-command@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -no-color&lt;/span&gt;
    &lt;span class="na"&gt;working_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}&lt;/span&gt;
    &lt;span class="na"&gt;max_attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;retry_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will execute the command up to 3 times with a 10-second interval between retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Usage: Random Retry Interval
&lt;/h2&gt;

&lt;p&gt;Since the action is implemented as a shell script, you can easily add randomness to the retry interval:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;corrupt952/retry-command@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -no-color&lt;/span&gt;
    &lt;span class="na"&gt;working_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}&lt;/span&gt;
    &lt;span class="na"&gt;retry_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$((RANDOM % 31))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will set a random retry interval between 0 and 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capturing Command Output
&lt;/h2&gt;

&lt;p&gt;The main reason I created this action was to capture the command output.&lt;br&gt;
Here's how you can do that:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;corrupt952/retry-command@v1&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_plan&lt;/span&gt;
  &lt;span class="na"&gt;continue-on-error&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;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -no-color&lt;/span&gt;
    &lt;span class="na"&gt;working_directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}&lt;/span&gt;
    &lt;span class="na"&gt;max_attempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.terraform_plan.outcome == 'failure'&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;echo "Exit code: ${{ steps.terraform_plan.outputs.exit_code }}"&lt;/span&gt;
    &lt;span class="s"&gt;echo "Result: ${{ steps.terraform_plan.outputs.result }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Currently, the action captures the result of the last successful or failed attempt.&lt;/p&gt;

&lt;p&gt;This feature allows you to easily use the results of the retry-command in other actions. &lt;br&gt;
or example, I use this to share Terraform plan results on GitHub, making it simple to review and discuss infrastructure changes within pull requests.&lt;/p&gt;

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

&lt;p&gt;If you need similar functionality in your GitHub Actions workflows, feel free to give this action a try.&lt;br&gt;
It's particularly useful when you need to both retry commands and capture their output.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Easy Notes in VSCode: Scraps</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Thu, 23 May 2024 17:30:31 +0000</pubDate>
      <link>https://dev.to/corrupt952/easy-notes-in-vscode-scraps-5gf2</link>
      <guid>https://dev.to/corrupt952/easy-notes-in-vscode-scraps-5gf2</guid>
      <description>&lt;p&gt;Hello, I'm &lt;a href="https://x.com/corrupt952" rel="noopener noreferrer"&gt;K@zuki.&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, I want to introduce a new VSCode extension I developed called &lt;code&gt;Scraps&lt;/code&gt;. This extension is a handy tool for taking quick notes while coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;Scraps&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Scraps&lt;/code&gt; is a simple extension that allows you to write notes in the sidebar of VSCode. This extension helps you quickly jot down ideas or reminders that come up during development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=corrupt952.scraps" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=corrupt952.scraps&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Easy Note Creation&lt;/strong&gt;: You can write notes directly in the sidebar, making it easy to jot down thoughts quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save Functionality&lt;/strong&gt;: Notes are saved even after closing VSCode, so you can safely store your thoughts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple UI&lt;/strong&gt;: The intuitive editor allows you to input notes in Markdown format.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Install
&lt;/h2&gt;

&lt;p&gt;Search for &lt;code&gt;Scraps&lt;/code&gt; in the VSCode Extensions marketplace and install it.&lt;/p&gt;

&lt;p&gt;You'll see an icon in the Activity Bar that looks like a notepad with a pencil. Click this icon to open the sidebar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjqvjm8rkrgtj5vc55ei4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjqvjm8rkrgtj5vc55ei4.png" alt="screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open VSCode and click the &lt;code&gt;Scraps&lt;/code&gt; icon in the Activity Bar.&lt;/li&gt;
&lt;li&gt;Enter your text freely in the note area displayed in the sidebar.&lt;/li&gt;
&lt;li&gt;No special save operation is needed; your notes are automatically saved.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Background of Development
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Scraps&lt;/code&gt; was born out of my need to take quick notes during daily coding sessions. Although I use other main note-taking tools, I often need to temporarily store notes that don't need to be kept long-term. I developed &lt;code&gt;Scraps&lt;/code&gt; to fulfill this need for a simple, VSCode-contained note-taking tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;Some possible use cases for &lt;code&gt;Scraps&lt;/code&gt; include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing notes or tasks that you don't want to include directly in your code.&lt;/li&gt;
&lt;li&gt;Temporarily storing commands or tokens until you can save them elsewhere.&lt;/li&gt;
&lt;li&gt;Keeping a simple to-do list for the day's tasks.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;code&gt;Scraps&lt;/code&gt; is still in its early stages, but I plan to continue adding features and making improvements. If you have any feedback or suggestions, please let me know through the marketplace review section or the GitHub repository.&lt;/p&gt;

&lt;p&gt;I hope &lt;code&gt;Scraps&lt;/code&gt; enhances your coding experience!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/corrupt952/scraps" rel="noopener noreferrer"&gt;https://github.com/corrupt952/scraps&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>extensions</category>
    </item>
    <item>
      <title>Easy Notes in VSCode: Scraps</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Thu, 23 May 2024 17:30:31 +0000</pubDate>
      <link>https://dev.to/corrupt952/easy-notes-in-vscode-scraps-43ld</link>
      <guid>https://dev.to/corrupt952/easy-notes-in-vscode-scraps-43ld</guid>
      <description>&lt;p&gt;Hello, I'm &lt;a href="https://x.com/corrupt952" rel="noopener noreferrer"&gt;K@zuki.&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, I want to introduce a new VSCode extension I developed called &lt;code&gt;Scraps&lt;/code&gt;. This extension is a handy tool for taking quick notes while coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;Scraps&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Scraps&lt;/code&gt; is a simple extension that allows you to write notes in the sidebar of VSCode. This extension helps you quickly jot down ideas or reminders that come up during development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=corrupt952.scraps" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=corrupt952.scraps&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Easy Note Creation&lt;/strong&gt;: You can write notes directly in the sidebar, making it easy to jot down thoughts quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save Functionality&lt;/strong&gt;: Notes are saved even after closing VSCode, so you can safely store your thoughts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple UI&lt;/strong&gt;: The intuitive editor allows you to input notes in Markdown format.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Install
&lt;/h2&gt;

&lt;p&gt;Search for &lt;code&gt;Scraps&lt;/code&gt; in the VSCode Extensions marketplace and install it.&lt;/p&gt;

&lt;p&gt;You'll see an icon in the Activity Bar that looks like a notepad with a pencil. Click this icon to open the sidebar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjqvjm8rkrgtj5vc55ei4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjqvjm8rkrgtj5vc55ei4.png" alt="screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open VSCode and click the &lt;code&gt;Scraps&lt;/code&gt; icon in the Activity Bar.&lt;/li&gt;
&lt;li&gt;Enter your text freely in the note area displayed in the sidebar.&lt;/li&gt;
&lt;li&gt;No special save operation is needed; your notes are automatically saved.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Background of Development
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Scraps&lt;/code&gt; was born out of my need to take quick notes during daily coding sessions. Although I use other main note-taking tools, I often need to temporarily store notes that don't need to be kept long-term. I developed &lt;code&gt;Scraps&lt;/code&gt; to fulfill this need for a simple, VSCode-contained note-taking tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;Some possible use cases for &lt;code&gt;Scraps&lt;/code&gt; include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing notes or tasks that you don't want to include directly in your code.&lt;/li&gt;
&lt;li&gt;Temporarily storing commands or tokens until you can save them elsewhere.&lt;/li&gt;
&lt;li&gt;Keeping a simple to-do list for the day's tasks.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;code&gt;Scraps&lt;/code&gt; is still in its early stages, but I plan to continue adding features and making improvements. If you have any feedback or suggestions, please let me know through the marketplace review section or the GitHub repository.&lt;/p&gt;

&lt;p&gt;I hope &lt;code&gt;Scraps&lt;/code&gt; enhances your coding experience!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/corrupt952/scraps" rel="noopener noreferrer"&gt;https://github.com/corrupt952/scraps&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>extensions</category>
    </item>
    <item>
      <title>Debugging Renovate locally</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Tue, 09 May 2023 22:00:00 +0000</pubDate>
      <link>https://dev.to/corrupt952/debugging-renovate-locally-4k8</link>
      <guid>https://dev.to/corrupt952/debugging-renovate-locally-4k8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you are using Renovate, it is important to check whether the settings are properly configured.&lt;/p&gt;

&lt;p&gt;This article explains how to debug Renovate in a local environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;The following prerequisites are required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate GitHub's Classic PAT and set it in the environment variable such as &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;If it's only dry-run, read permission to the repository should be sufficient.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verification Procedure
&lt;/h2&gt;

&lt;p&gt;Just execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RENOVATE_CONFIG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.github/renovate.json5 &lt;span class="se"&gt;\&lt;/span&gt;
  npx renovate &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--require-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ignored &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;full &lt;span class="se"&gt;\&lt;/span&gt;
    corrupt952/home-apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check whether the target is correct and whether a PR is created by using stats or messages displayed in &lt;code&gt;DRY-RUN&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ RENOVATE_CONFIG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.github/renovate.json5 &lt;span class="se"&gt;\&lt;/span&gt;
    npx renovate &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--require-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ignored &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;full &lt;span class="se"&gt;\&lt;/span&gt;
      corrupt952/home-apps
 INFO: Repository started &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;corrupt952/home-apps&lt;span class="o"&gt;)&lt;/span&gt;
       &lt;span class="s2"&gt;"renovateVersion"&lt;/span&gt;: &lt;span class="s2"&gt;"35.71.5"&lt;/span&gt;
 INFO: Dependency extraction &lt;span class="nb"&gt;complete&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;corrupt952/home-apps, &lt;span class="nv"&gt;baseBranch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;
       &lt;span class="s2"&gt;"stats"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
         &lt;span class="s2"&gt;"managers"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
           &lt;span class="s2"&gt;"argocd"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"fileCount"&lt;/span&gt;: 5, &lt;span class="s2"&gt;"depCount"&lt;/span&gt;: 6&lt;span class="o"&gt;}&lt;/span&gt;,
           &lt;span class="s2"&gt;"kubernetes"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"fileCount"&lt;/span&gt;: 1, &lt;span class="s2"&gt;"depCount"&lt;/span&gt;: 2&lt;span class="o"&gt;}&lt;/span&gt;,
           &lt;span class="s2"&gt;"regex"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"fileCount"&lt;/span&gt;: 5, &lt;span class="s2"&gt;"depCount"&lt;/span&gt;: 15&lt;span class="o"&gt;}&lt;/span&gt;
         &lt;span class="o"&gt;}&lt;/span&gt;,
         &lt;span class="s2"&gt;"total"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"fileCount"&lt;/span&gt;: 11, &lt;span class="s2"&gt;"depCount"&lt;/span&gt;: 23&lt;span class="o"&gt;}&lt;/span&gt;
       &lt;span class="o"&gt;}&lt;/span&gt;
 INFO: DRY-RUN: Would ensure Dependency Dashboard &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;corrupt952/home-apps&lt;span class="o"&gt;)&lt;/span&gt;
       &lt;span class="s2"&gt;"title"&lt;/span&gt;: &lt;span class="s2"&gt;"Dependency Dashboard"&lt;/span&gt;
 INFO: DRY-RUN: Would save repository cache. &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;corrupt952/home-apps&lt;span class="o"&gt;)&lt;/span&gt;
 INFO: Repository finished &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;corrupt952/home-apps&lt;span class="o"&gt;)&lt;/span&gt;
       &lt;span class="s2"&gt;"cloned"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;,
       &lt;span class="s2"&gt;"durationMs"&lt;/span&gt;: 6888
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Brief explanation of the command
&lt;/h3&gt;

&lt;p&gt;The following is an explanation of the command mentioned in the verification procedure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RENOVATE_CONFIG_FILE=.github/renovate.json5&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Specifies the path of the configuration file you want to test the operation of.&lt;/li&gt;
&lt;li&gt;The configuration file contains the information necessary for Renovate to operate.&lt;/li&gt;
&lt;li&gt;For details, refer to the &lt;a href="https://docs.renovatebot.com/getting-started/running/#global-config" rel="noopener noreferrer"&gt;official Renovate documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;npx renovate&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Since you want to try renovate easily without installing it individually, you are running renovate using npx.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;token=$GITHUB_TOKEN&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Specifies the PAT generated in advance.&lt;/li&gt;
&lt;li&gt;PAT is necessary for Renovate to access the GitHub API.&lt;/li&gt;
&lt;li&gt;For details, refer to the &lt;a href="https://docs.renovatebot.com/self-hosted-configuration/#token" rel="noopener noreferrer"&gt;official Renovate documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;schedule=""&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Since it may not be executed if the schedule is defined in the settings, an empty string is specified.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;require-config=ignored&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Prevents the remote repository's configuration file from being read.&lt;/li&gt;
&lt;li&gt;If the remote repository's configuration file is read, it will be merged with the local configuration file, which may result in unnecessary PRs being generated.&lt;/li&gt;
&lt;li&gt;For details, refer to the &lt;a href="https://docs.renovatebot.com/self-hosted-configuration/#requireconfig" rel="noopener noreferrer"&gt;official Renovate documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;dry-run=full&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;It outputs messages to standard output without actually creating, updating, or deleting PRs.&lt;/li&gt;
&lt;li&gt;For details, refer to the &lt;a href="https://docs.renovatebot.com/self-hosted-configuration/#dryrun" rel="noopener noreferrer"&gt;official Renovate documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;corrupt952/home-apps&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Specifies the repository targeted by Renovate.&lt;/li&gt;
&lt;li&gt;In this example, &lt;code&gt;corrupt952/home-apps&lt;/code&gt; is targeted.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Other cases that are likely to occur when verifying the operation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  I want to validate a branch other than the default branch.
&lt;/h3&gt;

&lt;p&gt;Execute by specifying &lt;code&gt;RENOVATE_BASE_BRANCHES&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In the case of targeting the dev branch&lt;/span&gt;
&lt;span class="nv"&gt;RENOVATE_BASE_BRANCHES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;RENOVATE_CONFIG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.github/renovate.json5 &lt;span class="se"&gt;\&lt;/span&gt;
  npx renovate &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--require-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ignored &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;full &lt;span class="se"&gt;\&lt;/span&gt;
    corrupt952/home-apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  I want to create a PR.
&lt;/h3&gt;

&lt;p&gt;Confirm that PAT has write permission to the repository and remove the &lt;code&gt;--dry-run&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RENOVATE_CONFIG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.github/renovate.json5 &lt;span class="se"&gt;\&lt;/span&gt;
  npx renovate &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--require-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ignored &lt;span class="se"&gt;\&lt;/span&gt;
    corrupt952/home-apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This article explained how to debug Renovate locally.&lt;/p&gt;

&lt;p&gt;If you are using Renovate, it is important to check whether the settings are properly configured.&lt;/p&gt;

&lt;p&gt;Please execute the procedure explained in this article to use Renovate normally.&lt;/p&gt;

</description>
      <category>renovate</category>
      <category>devops</category>
    </item>
    <item>
      <title>Change the way you access your home network using Cloudflare Tunnel</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Sun, 07 May 2023 22:00:00 +0000</pubDate>
      <link>https://dev.to/corrupt952/change-the-way-you-access-your-home-network-using-cloudflare-tunnel-bmo</link>
      <guid>https://dev.to/corrupt952/change-the-way-you-access-your-home-network-using-cloudflare-tunnel-bmo</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Recently, I changed my access method to my home network using Cloudflare Tunnel. This article will introduce that method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Old Configuration
&lt;/h2&gt;

&lt;p&gt;Previously, I accessed each service on my home cluster using VPN connections. The following is a diagram of that configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9br24bf8bb6cke49mif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9br24bf8bb6cke49mif.png" alt="old architecture" width="698" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New Configuration
&lt;/h2&gt;

&lt;p&gt;By using Cloudflare Tunnel, I eliminated the need for VPN connections and made it possible to access my home network without direct external exposure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxw2o71atypkrwrj9jd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxw2o71atypkrwrj9jd8.png" alt="new arhictecture" width="771" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros/Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pros

&lt;ul&gt;
&lt;li&gt;No need to expose servers/home networks externally&lt;/li&gt;
&lt;li&gt;Can benefit from Cloudflare CDN (security, IPv6 support, etc.)&lt;/li&gt;
&lt;li&gt;Can restrict access to specific domains or paths&lt;/li&gt;
&lt;li&gt;Can access services from external sources (compared to old configuration)&lt;/li&gt;
&lt;li&gt;No need to set up a VPN server (compared to old configuration)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Cons

&lt;ul&gt;
&lt;li&gt;No need to transfer, but need to manage the Apex Domain by Clouflare&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Changes in k8s Cluster Configuration
&lt;/h3&gt;

&lt;p&gt;Several changes were required for the new configuration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add

&lt;ul&gt;
&lt;li&gt;cloudflared ... necessary to tunnel access from Cloudflare&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Eliminate

&lt;ul&gt;
&lt;li&gt;MetalLB ... not necessary because cloudflared directly accesses each Service&lt;/li&gt;
&lt;li&gt;Nginx Ingress ... same as above&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following article was useful in setting up Cloudflare Tunnel for a home cluster.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://zenn.dev/yakumo/articles/b1f0e1115cb5b6" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--5yHMTGXQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/zenn/image/upload/s--Fx4j112U--/c_fit%252Cg_north_west%252Cl_text:notosansjp-medium.otf_55:Cloudflare%252520Tunnel%2525E3%252581%2525A7%2525E8%252587%2525AA%2525E5%2525AE%252585%2525E3%252582%2525B5%2525E3%252583%2525BC%2525E3%252583%252590%2525E3%252583%2525BC%2525E3%252582%252592%2525E5%252585%2525AC%2525E9%252596%25258B%2525E3%252581%252599%2525E3%252582%25258B%252528Free%2525E3%252583%252597%2525E3%252583%2525A9%2525E3%252583%2525B3OK%252529%252Cw_1010%252Cx_90%252Cy_100/g_south_west%252Cl_text:notosansjp-medium.otf_37:Yakumo%252520Saki%252Cx_203%252Cy_121/g_south_west%252Ch_90%252Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2ZlMGQ5ZWExZTIuanBlZw%3D%3D%252Cr_max%252Cw_90%252Cx_87%252Cy_95/v1627283836/default/og-base-w1200-v2.png" height="" class="m-0" width=""&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://zenn.dev/yakumo/articles/b1f0e1115cb5b6" rel="noopener noreferrer" class="c-link"&gt;
          Cloudflare Tunnelで自宅サーバーを公開する(FreeプランOK)
        &lt;/a&gt;
      &lt;/h2&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ihMcDnEc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://static.zenn.studio/images/logo-transparent.png" width="315" height="315"&gt;
        zenn.dev
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Restricting Access to Specific Domains or Paths
&lt;/h2&gt;

&lt;p&gt;It is possible to restrict access to specific domains or paths, thus improving security.&lt;/p&gt;

&lt;p&gt;For example, consider setting up WordPress in a Kubernetes cluster to be accessible from external sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauaeozax01ewklbf48ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fauaeozax01ewklbf48ho.png" alt="wordpress architecture" width="684" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, it is possible to only authenticate with Cloudflare Zero Trust for paths starting with &lt;code&gt;/wp-login&lt;/code&gt; or &lt;code&gt;/wp-admin&lt;/code&gt;, without requiring authentication for normal requests.&lt;/p&gt;

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

&lt;p&gt;This article introduced the change in access method to my home network using Cloudflare Tunnel. This new configuration has benefits such as improved security and IPv6 support. Additionally, it is possible to configure it with security in mind by restricting access to specific domains or paths.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/corrupt952"&gt;
        corrupt952
      &lt;/a&gt; / &lt;a href="https://github.com/corrupt952/home-apps"&gt;
        home-apps
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Home k8s manifests&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Repository for the home kubernetes cluster.&lt;/p&gt;
&lt;p&gt;This repository manages the manifests of the home kubernetes cluster.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;
⚠️ Archived notice&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This repository is archived.
It has been moved to &lt;a href="https://github.com/corrupt952/home"&gt;corrupt952/home&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup Argo CD&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --create-namespace --namespace argocd
kubectl -n argocd port-forward deploy/argocd-server 8080:8080
argocd login \
    --insecure \
    --grpc-web \
    --username admin \
    --password &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;$(&lt;/span&gt;kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;{.data.password}&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;|&lt;/span&gt; base64 -d&lt;span class="pl-pds"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; \
    localhost:8080
argocd app create argocd-config \
    --insecure \
    --grpc-web \
    --repo https://github.com/corrupt952/home-apps.git \
    --path argocd-config/base \
    --dest-namespace argocd \
    --dest-server https://kubernetes.default.svc \
    --sync-policy automated \
    --auto-prune \
    --revision main&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>kubernetes</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Trying Argo CD's Multiple sources</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Sat, 06 May 2023 22:00:00 +0000</pubDate>
      <link>https://dev.to/corrupt952/trying-argo-cds-multiple-sources-41f1</link>
      <guid>https://dev.to/corrupt952/trying-argo-cds-multiple-sources-41f1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Argo CD has a feature called Multiple sources.&lt;br&gt;&lt;br&gt;
This feature is in beta, but it allows you to build an application by retrieving configuration elements from different sources such as Helm Chart and Kustomize.&lt;/p&gt;

&lt;p&gt;In this article, we will look at how Multiple sources can be useful and how to use it for deploying WordPress.&lt;/p&gt;
&lt;h2&gt;
  
  
  Benefits of Multiple sources
&lt;/h2&gt;

&lt;p&gt;Multiple sources are useful in the following cases:&lt;/p&gt;
&lt;h3&gt;
  
  
  Separation of Helm Chart and values.yaml
&lt;/h3&gt;

&lt;p&gt;You can separate the reference to Helm Chart and values.yaml into different repositories or directories.&lt;br&gt;&lt;br&gt;
This makes version management of the application easier.&lt;/p&gt;
&lt;h3&gt;
  
  
  Managing multiple Helm Charts in one Application
&lt;/h3&gt;

&lt;p&gt;Using Multiple sources, you can manage multiple Helm Charts in one Application.&lt;br&gt;&lt;br&gt;
This allows for more flexible management of the application's configuration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying WordPress using Multiple sources
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Why WordPress?
&lt;/h3&gt;

&lt;p&gt;Bitnami's WordPress Chart has a problem where the DB password is regenerated when the pod is restarted. Therefore, it is necessary to separate the DB from the WordPress Chart, which is perfect for Multiple sources.&lt;/p&gt;
&lt;h3&gt;
  
  
  Defining an Application for WordPress
&lt;/h3&gt;

&lt;p&gt;Let's use Multiple sources to deploy WordPress.&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&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;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;wordpress&lt;/span&gt;
  &lt;span class="na"&gt;finalizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;resources-finalizer.argocd.argoproj.io&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Make corrupt952/home-apps available for $manfiest&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manifest&lt;/span&gt;
      &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/corrupt952/home-apps&lt;/span&gt;

    &lt;span class="c1"&gt;# Load resources defined in corrupt952/home-apps/wordpress/base&lt;/span&gt;
    &lt;span class="c1"&gt;# Manage secrets such as passwords used by mariadb and wordpress&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;wordpress/base&lt;/span&gt;
      &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/corrupt952/home-apps&lt;/span&gt;

    &lt;span class="c1"&gt;# Load bitnami/mariadb Chart and corrupt952/home-apps/values/mariadb.yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
      &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://charts.bitnami.com/bitnami&lt;/span&gt;
      &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.1.5&lt;/span&gt;
      &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;releaseName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
        &lt;span class="na"&gt;valueFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$manifest/values/mariadb.yaml&lt;/span&gt;

    &lt;span class="c1"&gt;# Load bitnami/wordpress Chart and corrupt952/home-apps/values/wordpress.yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
      &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://charts.bitnami.com/bitnami&lt;/span&gt;
      &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16.0.4&lt;/span&gt;
      &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;releaseName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
        &lt;span class="na"&gt;valueFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$manifest/values/wordpress.yaml&lt;/span&gt;

  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By defining the previously &lt;code&gt;source&lt;/code&gt; definition as &lt;code&gt;sources&lt;/code&gt;, you can define multiple sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  ref: manifest
&lt;/h3&gt;

&lt;p&gt;The first definition is to refer to values.yaml in corrupt952/home-apps in the second and third sources.&lt;/p&gt;

&lt;p&gt;In the case of &lt;code&gt;ref&lt;/code&gt;, resources in the repository are not loaded, so it is important to remember to define it when referring to a file.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manifest&lt;/span&gt;
  &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;https://github.com/corrupt952/home-apps&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  path: wordpress/base
&lt;/h3&gt;

&lt;p&gt;The second definition is a source that manages resources with kustomize.&lt;/p&gt;

&lt;p&gt;Since MariaDB and WordPress need to share passwords, Sealed Secret is used to manage them, and this definition is added to load and create resources.&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="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;wordpress/base&lt;/span&gt;
  &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;https://github.com/corrupt952/home-apps&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  chart: mariadb
&lt;/h3&gt;

&lt;p&gt;The third definition is a source that loads the MariaDB Helm Chart.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;valueFiles&lt;/code&gt; is defined to refer to &lt;code&gt;values/mariadb.yaml&lt;/code&gt; in corrupt952/home-apps.&lt;/p&gt;

&lt;p&gt;In the era before Multiple sources, such definitions were not possible, and it was necessary to define Chart.yaml in the same repository as values.yaml.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
  &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;https://charts.bitnami.com/bitnami&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.1.5&lt;/span&gt;
  &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;releaseName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
    &lt;span class="na"&gt;valueFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$manifest/values/mariadb.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# values/mariadb.yaml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client&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;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
  &lt;span class="na"&gt;existingSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress-credentials&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  chart: wordpress
&lt;/h3&gt;

&lt;p&gt;The fourth definition is a source that loads the WordPress Helm Chart.&lt;/p&gt;

&lt;p&gt;Like MariaDB, it refers to &lt;code&gt;values/wordpress.yaml&lt;/code&gt; in corrupt952/home-apps.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
  &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;https://charts.bitnami.com/bitnami&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16.0.4&lt;/span&gt;
  &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;releaseName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
    &lt;span class="na"&gt;valueFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$manifest/values/wordpress.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# values/wordpress.yaml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client&lt;/span&gt;

&lt;span class="na"&gt;allowEmptyPassword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;existingSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress-credentials&lt;/span&gt;

&lt;span class="na"&gt;mariadb&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;false&lt;/span&gt;

&lt;span class="na"&gt;memcached&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;false&lt;/span&gt;

&lt;span class="na"&gt;externalDatabase&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;mariadb.wordpress.svc.cluster.local&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
  &lt;span class="na"&gt;existingSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress-credentials&lt;/span&gt;

&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We have seen an example of deploying WordPress using Multiple sources. While it is still in beta, using Multiple sources effectively can lead to appropriate separation and commonization of manifests and settings.&lt;/p&gt;

</description>
      <category>argocd</category>
      <category>wordpress</category>
      <category>helm</category>
    </item>
    <item>
      <title>Automatically Updating Helm Chart Referenced in Argo CD Using Renovate - Part 2</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Fri, 05 May 2023 19:23:42 +0000</pubDate>
      <link>https://dev.to/corrupt952/automatically-updating-helm-chart-referenced-in-argo-cd-using-renovate-part-2-37nb</link>
      <guid>https://dev.to/corrupt952/automatically-updating-helm-chart-referenced-in-argo-cd-using-renovate-part-2-37nb</guid>
      <description>&lt;p&gt;Yesterday, I posted an article titled "Automatically Updating Helm Chart Referenced in Argo CD Using Renovate."&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/corrupt952" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F959720%2F3f7e6cac-308d-48f8-8b4f-3eb97cf30a77.jpeg" alt="corrupt952"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/corrupt952/automatically-updating-helm-charts-referenced-by-argo-cd-with-renovate-5g79" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Automatically Updating Helm Charts Referenced by Argo CD with Renovate&lt;/h2&gt;
      &lt;h3&gt;K@zuki. ・ May 4 '23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#argocd&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#renovate&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kubernetes&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;That method used regexManagers to retrieve version information from GitHub releases specified in the configuration. However, this time I will introduce an even easier and simpler method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap of Previous Method
&lt;/h2&gt;

&lt;p&gt;The previous method involved adding regexManagers and comments to the lines to be updated.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

  &lt;span class="c1"&gt;// renovate.json5&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;regexManagers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datasourceTemplate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github-releases&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fileMatch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;argocd-config/base/.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.yaml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matchStrings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// e.g.) targetRevision: 5.31.0 # renovate: depName=argoproj/argo-helm extractVersion=^argo-cd-(?&amp;lt;version&amp;gt;.+)$&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; +targetRevision: +(?&amp;lt;currentValue&amp;gt;[^'&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; ]+) +# renovate: depName=(?&amp;lt;depName&amp;gt;[^ &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n]+) +(extractVersion=(?&amp;lt;extractVersion&amp;gt;[^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n]+))?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Add comments to the lines to be updated&lt;/span&gt;
&lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5.31.0&lt;/span&gt; &lt;span class="c1"&gt;# renovate: depName=argoproj/argo-helm extractVersion=^argo-cd-(?&amp;lt;version&amp;gt;.+)$&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This time, we will make the automatic update process even easier.&lt;/p&gt;
&lt;h2&gt;
  
  
  Making It Easier
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Read the Documentation
&lt;/h3&gt;

&lt;p&gt;To make the automatic update process even easier, we first read the documentation for Managers.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://docs.renovatebot.com/modules/manager/" rel="noopener noreferrer"&gt;
      docs.renovatebot.com
    &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;The reason for reading the documentation for Managers is that if there are already suitable Managers listed in the documentation, then you can simply write the configuration for that Manager and it will automatically become an update target.&lt;/p&gt;

&lt;p&gt;If it is not listed, then write regexManagers, create an issue, or submit a pull request.&lt;/p&gt;

&lt;p&gt;Looking through the documentation, it seems that there is a Manager called &lt;code&gt;argocd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fpg87w8yqe9hejshlu76m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fpg87w8yqe9hejshlu76m.png" alt="screenshot"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://docs.renovatebot.com/modules/manager/argocd/" rel="noopener noreferrer"&gt;
      docs.renovatebot.com
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Although it is not clear whether it can be used as is, the Additional Information section suggests that it may be suitable for Argo CD applications.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The argocd manager has no fileMatch default patterns, so it won't match any files until you configure it with a pattern. This is because there is no commonly accepted file/directory naming convention for argocd YAML files and we don't want to check every single *.yaml file in repositories just in case any of them have ArgoCD definitions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Define the Configuration
&lt;/h3&gt;

&lt;p&gt;If there is a definition for &lt;code&gt;regexManagers&lt;/code&gt;, delete it. Simply define the path to the files to be updated in &lt;code&gt;fileMatch&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;argocd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fileMatch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;argocd-config/base/.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.yaml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;FYI: &lt;a href="https://github.com/corrupt952/home-apps/blob/main/.github/renovate.json5#L19-L23" rel="noopener noreferrer"&gt;https://github.com/corrupt952/home-apps/blob/main/.github/renovate.json5#L19-L23&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Updating the configuration in this way will make it an update target. It is much simpler than the previous method.&lt;/p&gt;
&lt;h3&gt;
  
  
  PRs Created
&lt;/h3&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/39" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update helm release argo-cd to v5.31.1
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#39&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/39" rel="noopener noreferrer"&gt;&lt;time&gt;May 05, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/argoproj/argo-helm" rel="nofollow noopener noreferrer"&gt;argo-cd&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;5.31.0&lt;/code&gt; -&amp;gt; &lt;code&gt;5.31.1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Release Notes&lt;/h3&gt;

argoproj/argo-helm
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/argoproj/argo-helm/releases/tag/argo-cd-5.31.1" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v5.31.1&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/argoproj/argo-helm/compare/argo-cd-5.31.0...argo-cd-5.31.1" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A Helm chart for Argo CD, a declarative, GitOps continuous delivery tool for Kubernetes.&lt;/p&gt;


&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/39" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/40" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update helm release kubernetes-dashboard to v6.0.7
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#40&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/40" rel="noopener noreferrer"&gt;&lt;time&gt;May 05, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/kubernetes/dashboard" rel="nofollow noopener noreferrer"&gt;kubernetes-dashboard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;6.0.6&lt;/code&gt; -&amp;gt; &lt;code&gt;6.0.7&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/40" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/41" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update helm release nfs-subdir-external-provisioner to v4.0.18
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#41&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/41" rel="noopener noreferrer"&gt;&lt;time&gt;May 05, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner" rel="nofollow noopener noreferrer"&gt;nfs-subdir-external-provisioner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;4.0.2&lt;/code&gt; -&amp;gt; &lt;code&gt;4.0.18&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Release Notes&lt;/h3&gt;

kubernetes-sigs/nfs-subdir-external-provisioner
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.18" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.18&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.17...nfs-subdir-external-provisioner-4.0.18" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.17" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.17&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.16...nfs-subdir-external-provisioner-4.0.17" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.16" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.16&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.15...nfs-subdir-external-provisioner-4.0.16" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.15" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.15&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.14...nfs-subdir-external-provisioner-4.0.15" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.14" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.14&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.13...nfs-subdir-external-provisioner-4.0.14" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.13" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.13&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.12...nfs-subdir-external-provisioner-4.0.13" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.12" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.12&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.11...nfs-subdir-external-provisioner-4.0.12" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.11" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.11&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.10...nfs-subdir-external-provisioner-4.0.11" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.10" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.10&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.9...nfs-subdir-external-provisioner-4.0.10" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.9" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.9&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.8...nfs-subdir-external-provisioner-4.0.9" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.8" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.8&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.6...nfs-subdir-external-provisioner-4.0.8" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.6" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.6&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.5...nfs-subdir-external-provisioner-4.0.6" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.5" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.5&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.4...nfs-subdir-external-provisioner-4.0.5" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.4" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.4&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/nfs-subdir-external-provisioner-4.0.3...nfs-subdir-external-provisioner-4.0.4" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/tag/nfs-subdir-external-provisioner-4.0.3" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.0.3&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes-sigs/nfs-subdir-external-provisioner/compare/v4.0.2...nfs-subdir-external-provisioner-4.0.3" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;nfs-subdir-external-provisioner is an automatic provisioner that used your &lt;em&gt;already configured&lt;/em&gt; NFS server, automatically creating Persistent Volumes.&lt;/p&gt;


&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/41" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/42" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update helm release sealed-secrets to v2.8.2
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#42&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/42" rel="noopener noreferrer"&gt;&lt;time&gt;May 05, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets" rel="nofollow noopener noreferrer"&gt;sealed-secrets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2.8.1&lt;/code&gt; -&amp;gt; &lt;code&gt;2.8.2&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/42" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/43" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update helm release wordpress to v16.0.4
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#43&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/43" rel="noopener noreferrer"&gt;&lt;time&gt;May 05, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://togithub.com/bitnami/charts/tree/main/bitnami/wordpress" rel="nofollow noopener noreferrer"&gt;wordpress&lt;/a&gt; (&lt;a href="https://togithub.com/bitnami/charts" rel="nofollow noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;16.0.2&lt;/code&gt; -&amp;gt; &lt;code&gt;16.0.4&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/43" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;That was a simple way to automatically update the Argo CD application using Renovate.&lt;/p&gt;

&lt;p&gt;Please try it out for yourself, even if you just install it for now.&lt;/p&gt;

</description>
      <category>argo</category>
      <category>renovate</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Automatically Updating Helm Charts Referenced by Argo CD with Renovate</title>
      <dc:creator>K@zuki.</dc:creator>
      <pubDate>Thu, 04 May 2023 12:48:58 +0000</pubDate>
      <link>https://dev.to/corrupt952/automatically-updating-helm-charts-referenced-by-argo-cd-with-renovate-5g79</link>
      <guid>https://dev.to/corrupt952/automatically-updating-helm-charts-referenced-by-argo-cd-with-renovate-5g79</guid>
      <description>&lt;p&gt;&lt;strong&gt;You can define it more easily in the new article.&lt;/strong&gt;&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/corrupt952" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F959720%2F3f7e6cac-308d-48f8-8b4f-3eb97cf30a77.jpeg" alt="corrupt952"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/corrupt952/automatically-updating-helm-chart-referenced-in-argo-cd-using-renovate-part-2-37nb" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Automatically Updating Helm Chart Referenced in Argo CD Using Renovate - Part 2&lt;/h2&gt;
      &lt;h3&gt;K@zuki. ・ May 5 '23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#argo&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#renovate&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kubernetes&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The manifests repo for my home k8s cluster, &lt;a href="https://github.com/corrupt952/home-apps" rel="noopener noreferrer"&gt;home-apps&lt;/a&gt;, is managed by Argo CD, and each application creates resources using Helm Charts.&lt;/p&gt;

&lt;p&gt;However, it is a hassle to manually update them during updates.&lt;br&gt;&lt;br&gt;
Therefore, I will use Renovate to automate the updates.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Renovate
&lt;/h2&gt;

&lt;p&gt;You can automatically update applications using Renovate.&lt;/p&gt;

&lt;p&gt;Refer to the official documentation to set up Renovate.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://docs.renovatebot.com/getting-started/installing-onboarding/" rel="noopener noreferrer"&gt;
      docs.renovatebot.com
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;You will need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install and configure Renovate's GitHub App&lt;/li&gt;
&lt;li&gt;Merge the PR for the added Renovate configuration file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once installed, you can check it on &lt;a href="https://app.renovatebot.com/dashboard" rel="noopener noreferrer"&gt;Renovate's dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fx90ji6lowbo39azwhc8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fx90ji6lowbo39azwhc8s.png" alt="menda renovate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Manifest Configuration
&lt;/h2&gt;

&lt;p&gt;Before moving on to automatic upgrades, let me briefly introduce the configuration of the manifests repo.&lt;br&gt;
Each manifest in the repo is configured as follows.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

argocd-config
└── base
    ├── argocd.yaml
    ├── ingress-nginx.yaml
    ├── kustomization.yaml
    ├── metallb.yaml
    └── sealed-secrets.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Each file defines resources using Helm Charts.&lt;br&gt;
The following is an excerpt from &lt;code&gt;argocd.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&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;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo-cd&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://argoproj.github.io/argo-helm&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5.31.0&lt;/span&gt;
    &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Automatic Upgrade
&lt;/h2&gt;

&lt;p&gt;You can use Renovate's regexManagers to upgrade.&lt;/p&gt;

&lt;p&gt;I don't like to write regexManagers for each application, so I specify the target repository and the release tag format as follows.&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;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5.31.0&lt;/span&gt; &lt;span class="c1"&gt;# renovate: depName=argoproj/argo-helm extractVersion=^argo-cd-(?&amp;lt;version&amp;gt;.+)$&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Target repository&lt;/li&gt;
&lt;li&gt;Release tag format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Specify them as Renovate's update targets.&lt;/p&gt;
&lt;h3&gt;
  
  
  Release Tag Format
&lt;/h3&gt;

&lt;p&gt;Since many Helm Charts managed by each repository have a prefix, the release tag of each repository contains not only the version string specified in targetRevision, but also a prefix.&lt;/p&gt;

&lt;p&gt;The format of the release tags for each repository is as follows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/argoproj/argo-helm/releases" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt; … &lt;code&gt;argo-cd-{VERSION}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kubernetes/ingress-nginx/releases" rel="noopener noreferrer"&gt;Ingress NGINX Controller&lt;/a&gt; … &lt;code&gt;helm-chart-{VERSION}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bitnami-labs/sealed-secrets/releases" rel="noopener noreferrer"&gt;Sealed Secrets&lt;/a&gt; … &lt;code&gt;helm-v{VERSION}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/metallb/metallb/releases" rel="noopener noreferrer"&gt;MetalLB&lt;/a&gt; … &lt;code&gt;metallb-chart-{VERSION}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Define regexManagers
&lt;/h3&gt;

&lt;p&gt;Based on this information, define regexManagers.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;regexManagers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;datasourceTemplate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github-releases&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fileMatch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;argocd-config/base/.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.yaml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matchStrings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; +targetRevision: +(?&amp;lt;currentValue&amp;gt;[^'&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; ]+) +# renovate: depName=(?&amp;lt;depName&amp;gt;[^ &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n]+) +(extractVersion=(?&amp;lt;extractVersion&amp;gt;[^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n]+))?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Simply adding this definition will make the version of the Helm Charts in the manifest automatically updated.&lt;/p&gt;
&lt;h3&gt;
  
  
  Created PRs
&lt;/h3&gt;

&lt;p&gt;The following PRs were created by the regexManagers defined this time and became as expected.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/24" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update dependency kubernetes/ingress-nginx to v4.6.0
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#24&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/24" rel="noopener noreferrer"&gt;&lt;time&gt;May 03, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx" rel="nofollow noopener noreferrer"&gt;kubernetes/ingress-nginx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;minor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;4.3.0&lt;/code&gt; -&amp;gt; &lt;code&gt;4.6.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Release Notes&lt;/h3&gt;

kubernetes/ingress-nginx
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/releases/tag/helm-chart-4.6.0" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.6.0&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/compare/helm-chart-4.5.2...helm-chart-4.6.0" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/releases/tag/helm-chart-4.5.2" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.5.2&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/compare/helm-chart-4.5.0...helm-chart-4.5.2" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/releases/tag/helm-chart-4.5.0" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.5.0&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/compare/helm-chart-4.4.2...helm-chart-4.5.0" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/releases/tag/helm-chart-4.4.2" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.4.2&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/compare/helm-chart-4.4.0...helm-chart-4.4.2" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/releases/tag/helm-chart-4.4.0" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v4.4.0&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/kubernetes/ingress-nginx/compare/helm-chart-4.3.0...helm-chart-4.4.0" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer&lt;/p&gt;


&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/24" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/23" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update dependency bitnami-labs/sealed-secrets to v2.8.2
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#23&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/23" rel="noopener noreferrer"&gt;&lt;time&gt;May 03, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets" rel="nofollow noopener noreferrer"&gt;bitnami-labs/sealed-secrets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;minor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2.7.0&lt;/code&gt; -&amp;gt; &lt;code&gt;2.8.2&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Release Notes&lt;/h3&gt;

bitnami-labs/sealed-secrets
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.8.2" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.8.2&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.8.1...helm-v2.8.2" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.8.1" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.8.1&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.8.0...helm-v2.8.1" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.8.0" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.8.0&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.7.6...helm-v2.8.0" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.7.6" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.7.6&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.7.5...helm-v2.7.6" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.7.5" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.7.5&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.7.4...helm-v2.7.5" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.7.4" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.7.4&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.7.3...helm-v2.7.4" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.7.3" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.7.3&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.7.2...helm-v2.7.3" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.7.2" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.7.2&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.7.1...helm-v2.7.2" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/releases/tag/helm-v2.7.1" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v2.7.1&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/bitnami-labs/sealed-secrets/compare/helm-v2.7.0...helm-v2.7.1" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm chart for the sealed-secrets controller.&lt;/p&gt;


&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/23" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/22" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update dependency metallb/metallb to v0.13.9
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#22&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/22" rel="noopener noreferrer"&gt;&lt;time&gt;May 03, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/metallb/metallb" rel="nofollow noopener noreferrer"&gt;metallb/metallb&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0.13.7&lt;/code&gt; -&amp;gt; &lt;code&gt;0.13.9&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Release Notes&lt;/h3&gt;

metallb/metallb
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/metallb/metallb/releases/tag/v0.13.9" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v0.13.9&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/metallb/metallb/compare/v0.13.7...v0.13.9" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://metallb.universe.tf/release-notes/" rel="nofollow noopener noreferrer"&gt;https://metallb.universe.tf/release-notes/&lt;/a&gt; for details&lt;/p&gt;


&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/22" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/corrupt952/home-apps/pull/21" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        chore(deps): update dependency argoproj/argo-helm to v5.31.1
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#21&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fin%2F2740%3Fv%3D4" alt="renovate[bot] avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/apps/renovate" rel="noopener noreferrer"&gt;renovate[bot]&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/corrupt952/home-apps/pull/21" rel="noopener noreferrer"&gt;&lt;time&gt;May 03, 2023&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;&lt;a href="https://renovatebot.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/80b6571d15d8ec7de8bd9f85a9f42e288ee0735c499641c2a6d05b346d552a52/68747470733a2f2f6170702e72656e6f76617465626f742e636f6d2f696d616765732f62616e6e65722e737667" alt="Mend Renovate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR contains the following updates:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Update&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://togithub.com/argoproj/argo-helm" rel="nofollow noopener noreferrer"&gt;argoproj/argo-helm&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;patch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;5.31.0&lt;/code&gt; -&amp;gt; &lt;code&gt;5.31.1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Release Notes&lt;/h3&gt;

argoproj/argo-helm
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;&lt;a href="https://togithub.com/argoproj/argo-helm/releases/tag/argo-cd-5.31.1" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;v5.31.1&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://togithub.com/argoproj/argo-helm/compare/argo-cd-5.31.0...argo-cd-5.31.1" rel="nofollow noopener noreferrer"&gt;Compare Source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A Helm chart for Argo CD, a declarative, GitOps continuous delivery tool for Kubernetes.&lt;/p&gt;


&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Configuration&lt;/h3&gt;
&lt;p&gt;📅 &lt;strong&gt;Schedule&lt;/strong&gt;: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).&lt;/p&gt;
&lt;p&gt;🚦 &lt;strong&gt;Automerge&lt;/strong&gt;: Disabled by config. Please merge this manually once you are satisfied.&lt;/p&gt;
&lt;p&gt;♻ &lt;strong&gt;Rebasing&lt;/strong&gt;: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.&lt;/p&gt;
&lt;p&gt;🔕 &lt;strong&gt;Ignore&lt;/strong&gt;: Close this PR and you won't be reminded about this update again.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you want to rebase/retry this PR, check this box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This PR has been generated by &lt;a href="https://www.mend.io/free-developer-tools/renovate/" rel="nofollow noopener noreferrer"&gt;Mend Renovate&lt;/a&gt;. View repository job log &lt;a href="https://app.renovatebot.com/dashboard#github/corrupt952/home-apps" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/corrupt952/home-apps/pull/21" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Those were the steps to automatically update the Argo CD application with Renovate.&lt;/p&gt;

&lt;p&gt;You can use this for other things, so please try it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;I referred to aqua-renovate-config for the definition of regexManagers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aquaproj/aqua-renovate-config/blob/2f978289de753b39cb1053dc8ca44e0d6e911640/base.json" rel="noopener noreferrer"&gt;https://github.com/aquaproj/aqua-renovate-config/blob/2f978289de753b39cb1053dc8ca44e0d6e911640/base.json&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aquaproj" rel="noopener noreferrer"&gt;
        aquaproj
      &lt;/a&gt; / &lt;a href="https://github.com/aquaproj/aqua-renovate-config" rel="noopener noreferrer"&gt;
        aqua-renovate-config
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Renovate Configuration to update packages and registries of aqua
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;aqua-renovate-config&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://docs.renovatebot.com/config-presets/" rel="nofollow noopener noreferrer"&gt;Renovate Config Preset&lt;/a&gt; to update &lt;a href="https://aquaproj.github.io/" rel="nofollow noopener noreferrer"&gt;aqua&lt;/a&gt;, &lt;a href="https://github.com/aquaproj/aqua-installer" rel="noopener noreferrer"&gt;aqua-installer&lt;/a&gt;, packages, and registries.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Document&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Please see &lt;a href="https://aquaproj.github.io/docs/products/aqua-renovate-config" rel="nofollow noopener noreferrer"&gt;document&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Please see &lt;a href="https://github.com/aquaproj/aqua-renovate-configCONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/aquaproj/aqua-renovate-configLICENSE" rel="noopener noreferrer"&gt;MIT&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aquaproj/aqua-renovate-config" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>argocd</category>
      <category>renovate</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
