<?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: Bora Kilicoglu</title>
    <description>The latest articles on DEV Community by Bora Kilicoglu (@borakilicoglu).</description>
    <link>https://dev.to/borakilicoglu</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%2F64967%2F677670bf-d534-441d-abe4-c4b98f039d5f.png</url>
      <title>DEV Community: Bora Kilicoglu</title>
      <link>https://dev.to/borakilicoglu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/borakilicoglu"/>
    <language>en</language>
    <item>
      <title>ferman: A Tiny CLI for Inspecting and Freeing Busy Ports</title>
      <dc:creator>Bora Kilicoglu</dc:creator>
      <pubDate>Tue, 24 Mar 2026 16:40:46 +0000</pubDate>
      <link>https://dev.to/borakilicoglu/i-built-ferman-a-tiny-cli-for-inspecting-and-freeing-busy-ports-17e0</link>
      <guid>https://dev.to/borakilicoglu/i-built-ferman-a-tiny-cli-for-inspecting-and-freeing-busy-ports-17e0</guid>
      <description>&lt;p&gt;Busy ports are one of those tiny problems that interrupt real work far more often than they should.&lt;/p&gt;

&lt;p&gt;You start a dev server, a local database, a preview app, or a test runner, and something fails because a port is already in use. Then the usual dance begins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check what is using the port&lt;/li&gt;
&lt;li&gt;figure out whether it is safe to stop&lt;/li&gt;
&lt;li&gt;kill the process&lt;/li&gt;
&lt;li&gt;retry your actual work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That friction is exactly why I built &lt;strong&gt;ferman&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ferman&lt;/code&gt; is a small cross-platform CLI for inspecting busy ports, identifying the process behind them, and freeing them safely.&lt;/p&gt;

&lt;p&gt;It is built for developers, but also for AI agents, scripts, and local automation workflows that need deterministic output and low ambiguity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;The core problem is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a port is occupied&lt;/li&gt;
&lt;li&gt;you need to know what is using it&lt;/li&gt;
&lt;li&gt;you may want to free it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are already OS-level commands for this, but they are not consistent across platforms, and they are not especially friendly for automation.&lt;/p&gt;

&lt;p&gt;On macOS and Linux you might use &lt;code&gt;lsof&lt;/code&gt;.&lt;br&gt;
On Windows you might use &lt;code&gt;netstat&lt;/code&gt;, &lt;code&gt;tasklist&lt;/code&gt;, and &lt;code&gt;taskkill&lt;/code&gt;.&lt;br&gt;
If you are scripting around those commands, you also end up dealing with parsing, platform branching, and error handling.&lt;/p&gt;

&lt;p&gt;I wanted one clean command that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;works across platforms&lt;/li&gt;
&lt;li&gt;stays small&lt;/li&gt;
&lt;li&gt;is readable for humans&lt;/li&gt;
&lt;li&gt;is structured for machines&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What ferman does
&lt;/h2&gt;

&lt;p&gt;At the simplest level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ferman 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It validates the port, checks whether it is busy, identifies the matching process when possible, and asks before termination.&lt;/p&gt;

&lt;p&gt;There are a few core modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman 3000
ferman 3000 &lt;span class="nt"&gt;--force&lt;/span&gt;
ferman 3000 &lt;span class="nt"&gt;--dry&lt;/span&gt;
ferman 3000 &lt;span class="nt"&gt;--json&lt;/span&gt;
ferman 3000 &lt;span class="nt"&gt;--toon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also supports more AI-oriented flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman 3000 5173 5432 &lt;span class="nt"&gt;--json&lt;/span&gt;
ferman &lt;span class="nt"&gt;--common&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
ferman &lt;span class="nt"&gt;--plan&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
ferman &lt;span class="nt"&gt;--doctor&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
ferman &lt;span class="nt"&gt;--json-schema&lt;/span&gt;
ferman 3000 &lt;span class="nt"&gt;--watch&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Built for humans and agents
&lt;/h2&gt;

&lt;p&gt;One thing I cared about from the start was making the tool work well in two very different contexts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a developer using a terminal manually&lt;/li&gt;
&lt;li&gt;a script, CI job, or AI agent using structured output&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For humans, the CLI stays minimal and readable.&lt;/p&gt;

&lt;p&gt;For machines, the output is deterministic and stable.&lt;/p&gt;

&lt;p&gt;Example JSON output:&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;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PORT_RELEASED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"busy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"processes"&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="nl"&gt;"pid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&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="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"killed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Port released."&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;Example error output:&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;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INVALID_PORT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Port must be a whole number."&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;That matters because once the output contract is stable, the CLI becomes much easier to compose into larger workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-facing features
&lt;/h2&gt;

&lt;p&gt;Over time, I pushed the project beyond the original “free a port” use case.&lt;/p&gt;

&lt;p&gt;The current AI-facing surface includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;strong JSON contract&lt;/li&gt;
&lt;li&gt;stable machine error codes&lt;/li&gt;
&lt;li&gt;TOON output for compact LLM-oriented structured data&lt;/li&gt;
&lt;li&gt;multi-port support&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--common&lt;/code&gt; for common local development ports&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--plan&lt;/code&gt; for recommendation output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--doctor&lt;/code&gt; for local environment diagnosis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--json-schema&lt;/code&gt; for machine contract discovery&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--watch&lt;/code&gt; for repeated snapshot events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes &lt;code&gt;ferman&lt;/code&gt; more useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local AI coding agents&lt;/li&gt;
&lt;li&gt;shell automation&lt;/li&gt;
&lt;li&gt;CI helpers&lt;/li&gt;
&lt;li&gt;internal dev tooling&lt;/li&gt;
&lt;li&gt;MCP-style wrappers and structured integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A few examples
&lt;/h2&gt;

&lt;p&gt;Inspect without changing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman 3000 &lt;span class="nt"&gt;--dry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Release immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman 3000 &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check several ports at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman 3000 5173 5432 &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scan common dev ports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman &lt;span class="nt"&gt;--common&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask for the recommended next step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman 3000 &lt;span class="nt"&gt;--plan&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the output schema for integrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman &lt;span class="nt"&gt;--json-schema&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch a port over time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ferman 3000 &lt;span class="nt"&gt;--watch&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;The project is built in TypeScript and keeps dependencies intentionally small.&lt;/p&gt;

&lt;p&gt;Platform-specific behavior is abstracted behind separate adapters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The docs are built with VitePress, tests run with Vitest, and the package is published through GitHub Actions with trusted publishing.&lt;/p&gt;

&lt;p&gt;I also added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Prettier&lt;/li&gt;
&lt;li&gt;Husky&lt;/li&gt;
&lt;li&gt;smoke checks&lt;/li&gt;
&lt;li&gt;release checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So even though the tool is small, the release workflow is treated seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about the result
&lt;/h2&gt;

&lt;p&gt;I like that &lt;code&gt;ferman&lt;/code&gt; stays narrow.&lt;/p&gt;

&lt;p&gt;It does not try to be a general system monitor or a giant process manager.&lt;br&gt;
It focuses on one annoying workflow and makes it much easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;find what is using a port&lt;/li&gt;
&lt;li&gt;understand whether action is needed&lt;/li&gt;
&lt;li&gt;free the port when appropriate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough to be useful every day.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ferman 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/borakilicoglu/ferman" rel="noopener noreferrer"&gt;https://github.com/borakilicoglu/ferman&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/ferman" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ferman&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://borakilicoglu.github.io/ferman/" rel="noopener noreferrer"&gt;https://borakilicoglu.github.io/ferman/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build internal tooling, local automation, or AI-assisted developer workflows, I think this kind of CLI surface is worth investing in.&lt;/p&gt;

&lt;p&gt;Small tools with predictable interfaces compound well.&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>cli</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>sadrazam: A Dependency Hygiene CLI for JavaScript and TypeScript</title>
      <dc:creator>Bora Kilicoglu</dc:creator>
      <pubDate>Tue, 24 Mar 2026 13:57:23 +0000</pubDate>
      <link>https://dev.to/borakilicoglu/how-i-built-sadrazam-a-deterministic-dependency-hygiene-cli-for-javascript-and-typescript-26jg</link>
      <guid>https://dev.to/borakilicoglu/how-i-built-sadrazam-a-deterministic-dependency-hygiene-cli-for-javascript-and-typescript-26jg</guid>
      <description>&lt;p&gt;JavaScript and TypeScript projects get messy in a very predictable way.&lt;/p&gt;

&lt;p&gt;A package gets added for a short experiment.&lt;br&gt;
A build tool changes.&lt;br&gt;
A migration leaves old dependencies behind.&lt;br&gt;
A file stops being imported.&lt;br&gt;
An export survives three refactors for no good reason.&lt;/p&gt;

&lt;p&gt;A few months later, the project still works, but &lt;code&gt;package.json&lt;/code&gt; and parts of the codebase no longer reflect reality.&lt;/p&gt;

&lt;p&gt;That is why I built &lt;strong&gt;Sadrazam&lt;/strong&gt;: a dependency and code-hygiene CLI for JavaScript and TypeScript projects.&lt;/p&gt;

&lt;p&gt;Its goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;tell me what is actually used, what is not, and do it conservatively.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem with dependency cleanup tools
&lt;/h2&gt;

&lt;p&gt;A lot of tooling in this space is useful, but the tradeoff is often the same: once a tool gets too aggressive, trust drops.&lt;/p&gt;

&lt;p&gt;For me, dependency hygiene tooling needs a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deterministic analysis over aggressive guessing&lt;/li&gt;
&lt;li&gt;low false positives&lt;/li&gt;
&lt;li&gt;clear CLI and reporter contracts&lt;/li&gt;
&lt;li&gt;safe and narrow auto-fix behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did not want a tool that “feels smart” but quietly makes risky edits.&lt;br&gt;
I wanted a tool that behaves more like an engineer doing a careful review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Sadrazam does
&lt;/h2&gt;

&lt;p&gt;Sadrazam analyzes a project by reading &lt;code&gt;package.json&lt;/code&gt;, scanning source files, and comparing declared dependencies with actual usage.&lt;/p&gt;

&lt;p&gt;It currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unused &lt;code&gt;dependencies&lt;/code&gt; and &lt;code&gt;devDependencies&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;missing package declarations&lt;/li&gt;
&lt;li&gt;unused files&lt;/li&gt;
&lt;li&gt;unused exports&lt;/li&gt;
&lt;li&gt;package trace and export trace&lt;/li&gt;
&lt;li&gt;script-aware dependency detection&lt;/li&gt;
&lt;li&gt;workspace and monorepo scanning&lt;/li&gt;
&lt;li&gt;CommonJS and hybrid import support&lt;/li&gt;
&lt;li&gt;framework file scanning for &lt;code&gt;.vue&lt;/code&gt;, &lt;code&gt;.svelte&lt;/code&gt;, &lt;code&gt;.astro&lt;/code&gt;, and &lt;code&gt;.mdx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;safe &lt;code&gt;--fix&lt;/code&gt; and &lt;code&gt;--fix --format&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AI summaries via OpenAI, Anthropic, or Gemini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick example:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
npx sadrazam .
npx sadrazam . --trace typescript
npx sadrazam . --include unused-files,unused-exports
npx sadrazam . --fix --format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
      <category>cli</category>
    </item>
    <item>
      <title>ajan-sql: Giving AI Safe, Read-Only SQL Access with MCP</title>
      <dc:creator>Bora Kilicoglu</dc:creator>
      <pubDate>Mon, 23 Mar 2026 07:31:03 +0000</pubDate>
      <link>https://dev.to/borakilicoglu/ai-safe-mcp-server-for-sql-4jn4</link>
      <guid>https://dev.to/borakilicoglu/ai-safe-mcp-server-for-sql-4jn4</guid>
      <description>&lt;h1&gt;
  
  
  Safe SQL Access for AI with MCP
&lt;/h1&gt;

&lt;p&gt;Giving an AI direct database access sounds useful immediately, and dangerous a few seconds later.&lt;/p&gt;

&lt;p&gt;You want the model to inspect schema, understand relationships, run queries, and help with analysis. But you do not want it to mutate data, run unrestricted SQL, or pull back massive result sets because a prompt was vague.&lt;/p&gt;

&lt;p&gt;That is the motivation behind &lt;code&gt;ajan-sql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ajan-sql&lt;/code&gt; is a small MCP server for SQL that gives AI clients schema-aware, read-only database access with a guard layer in front of query execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;ajan-sql&lt;/code&gt; does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ajan-sql&lt;/code&gt; runs as an MCP server over stdio and connects through &lt;code&gt;DATABASE_URL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;SQLite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It exposes these tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server_info&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list_tables&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;describe_table&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list_relationships&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_schema&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run_readonly_query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;explain_query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sample_rows&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also exposes these schema resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;schema://snapshot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schema://table/{name}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple: give LLMs useful database visibility without handing them unsafe SQL execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;A lot of AI plus database demos jump from "the model can write SQL" straight to "let it query production-like data."&lt;/p&gt;

&lt;p&gt;That gap matters.&lt;/p&gt;

&lt;p&gt;If you want something usable in practice, you need a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema discovery&lt;/li&gt;
&lt;li&gt;safe defaults&lt;/li&gt;
&lt;li&gt;hard query restrictions&lt;/li&gt;
&lt;li&gt;predictable outputs for clients&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted something closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;psql plus schema awareness plus an AI-safe guard layer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But packaged in a way that works naturally with MCP clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  The safety model
&lt;/h2&gt;

&lt;p&gt;The core rule is that query execution must stay read-only and constrained.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ajan-sql&lt;/code&gt; enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SELECT&lt;/code&gt; only, including guarded &lt;code&gt;WITH ... SELECT&lt;/code&gt; queries&lt;/li&gt;
&lt;li&gt;rejection of &lt;code&gt;INSERT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rejection of &lt;code&gt;UPDATE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rejection of &lt;code&gt;DELETE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rejection of &lt;code&gt;DROP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rejection of &lt;code&gt;ALTER&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rejection of &lt;code&gt;TRUNCATE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;rejection of multi-statement SQL&lt;/li&gt;
&lt;li&gt;rejection of SQL comments&lt;/li&gt;
&lt;li&gt;default &lt;code&gt;LIMIT 100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;max &lt;code&gt;LIMIT 100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;max query timeout of 5 seconds&lt;/li&gt;
&lt;li&gt;max result size checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the model can inspect and analyze, but it cannot mutate the database through this server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling that is actually useful
&lt;/h2&gt;

&lt;p&gt;The schema tools are what make the server practical instead of just being a thin SQL wrapper.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;server_info&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns runtime details such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server version&lt;/li&gt;
&lt;li&gt;active SQL dialect&lt;/li&gt;
&lt;li&gt;available tools&lt;/li&gt;
&lt;li&gt;available resources&lt;/li&gt;
&lt;li&gt;readonly guard settings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;list_tables&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns visible tables and includes metadata such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema&lt;/li&gt;
&lt;li&gt;table name&lt;/li&gt;
&lt;li&gt;table comment&lt;/li&gt;
&lt;li&gt;estimated row count when available&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;describe_table&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;columns&lt;/li&gt;
&lt;li&gt;data types&lt;/li&gt;
&lt;li&gt;nullability&lt;/li&gt;
&lt;li&gt;default values&lt;/li&gt;
&lt;li&gt;primary key metadata&lt;/li&gt;
&lt;li&gt;unique metadata&lt;/li&gt;
&lt;li&gt;foreign key references&lt;/li&gt;
&lt;li&gt;index metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;list_relationships&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns foreign key relationships across the schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;search_schema&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Searches table names and column names with a simple substring match, which is useful when a client knows the concept but not the exact table.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;run_readonly_query&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Executes guarded &lt;code&gt;SELECT&lt;/code&gt; queries and returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normalized SQL&lt;/li&gt;
&lt;li&gt;row count&lt;/li&gt;
&lt;li&gt;duration&lt;/li&gt;
&lt;li&gt;column metadata&lt;/li&gt;
&lt;li&gt;rows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;explain_query&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Runs a guarded explain plan and returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;query timing&lt;/li&gt;
&lt;li&gt;raw plan&lt;/li&gt;
&lt;li&gt;a lightweight summary of the root plan node&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;sample_rows&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Returns a limited sample from a table and can optionally target selected columns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured outputs matter
&lt;/h2&gt;

&lt;p&gt;One thing I wanted from the beginning was output that works well for both humans and clients.&lt;/p&gt;

&lt;p&gt;So the tools return both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;content&lt;/code&gt; for a short human-readable summary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;structuredContent&lt;/code&gt; for machine-friendly consumption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes the server easier to use from MCP-compatible clients that want stable structured payloads instead of parsing JSON out of plain text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example setup
&lt;/h2&gt;

&lt;p&gt;Install it globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; ajan-sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with PostgreSQL:&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;DATABASE_DIALECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://USER:PASSWORD@HOST:PORT/DB ajan-sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MySQL example:&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;DATABASE_DIALECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysql &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysql://USER:PASSWORD@HOST:PORT/DB ajan-sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLite example:&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;DATABASE_DIALECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;file:/absolute/path/to/database.sqlite ajan-sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DATABASE_DIALECT&lt;/code&gt; defaults to &lt;code&gt;postgres&lt;/code&gt;, so PostgreSQL users only need to set &lt;code&gt;DATABASE_URL&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding &lt;code&gt;ajan-sql&lt;/code&gt; to an MCP client
&lt;/h2&gt;

&lt;p&gt;After installing &lt;code&gt;ajan-sql&lt;/code&gt;, add it to your MCP client as a stdio server.&lt;/p&gt;

&lt;p&gt;If your client can launch globally installed commands directly, use:&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;"ajan-sql"&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;"ajan-sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_DIALECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres://USER:PASSWORD@HOST:PORT/DB"&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;If you are developing from the repository instead of a global install, point the client at the built CLI:&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;"ajan-sql"&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;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/absolute/path/to/ajan-sql/dist/index.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_DIALECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres://USER:PASSWORD@HOST:PORT/DB"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For MySQL, set &lt;code&gt;DATABASE_DIALECT&lt;/code&gt; to &lt;code&gt;mysql&lt;/code&gt; and use a MySQL connection string.&lt;/p&gt;

&lt;p&gt;For SQLite, set &lt;code&gt;DATABASE_DIALECT&lt;/code&gt; to &lt;code&gt;sqlite&lt;/code&gt; and use a file URL such as:&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;"ajan-sql"&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;"ajan-sql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_DIALECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sqlite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file:/absolute/path/to/database.sqlite"&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 the server is added, MCP clients can discover the available tools and resources automatically and start using schema inspection, relationship discovery, guarded query execution, explain plans, and sample rows through the standard MCP flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this shape works well for AI workflows
&lt;/h2&gt;

&lt;p&gt;The point is not to let a model "do anything with SQL."&lt;/p&gt;

&lt;p&gt;The point is to give it enough visibility to explore schema, understand relationships, inspect sample data, and answer analytical questions while keeping execution bounded and read-only.&lt;/p&gt;

&lt;p&gt;That is the shape I wanted: useful enough for real AI workflows, constrained enough to trust in practice.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>sql</category>
      <category>ai</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
