<?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: Artyom Rabzonov</title>
    <description>The latest articles on DEV Community by Artyom Rabzonov (@ratamaha).</description>
    <link>https://dev.to/ratamaha</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%2F3927312%2Ff9181fc6-f4c7-4843-8b25-1f62aa92f0f1.jpg</url>
      <title>DEV Community: Artyom Rabzonov</title>
      <link>https://dev.to/ratamaha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ratamaha"/>
    <language>en</language>
    <item>
      <title>I built a one-command, whole-site AEO/GEO audit that hands your agent a plan.json</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Wed, 03 Jun 2026 10:53:08 +0000</pubDate>
      <link>https://dev.to/ratamaha/i-built-a-one-command-whole-site-aeogeo-audit-that-hands-your-agent-a-planjson-5b8d</link>
      <guid>https://dev.to/ratamaha/i-built-a-one-command-whole-site-aeogeo-audit-that-hands-your-agent-a-planjson-5b8d</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; - &lt;code&gt;ai-seo-magic-button&lt;/code&gt; is a free, MIT-licensed tool that crawls your whole site, runs ~39 AEO/GEO checks on every page, and outputs a &lt;code&gt;plan.json&lt;/code&gt; (plus markdown checklist) your AI agent executes. Installs three ways - Claude skill, Claude Code plugin, or MCP server in any host - no API keys for the core flow. &lt;code&gt;npx -y ai-seo-magic-button mcp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt; - AI search is decoupling from classic SEO: top-10 Google rank no longer means an LLM cites you. The "will an AI quote this page" list is different (structured data, extractable answer sections, llms.txt, freshness) and has to be consistent sitewide. Most tools monitor where you're cited and stop; the magic button turns a whole-site audit into a ranked, executable plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt; - We already ship two MCP engines (one scores a page for AEO/GEO, one tracks what LLMs cite). Running them page by page and triaging into a to-do list was the tedious part. The magic button is the orchestration layer that emits one artifact an agent can run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works (two-step flow)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the skill → get a plan. Crawls every page, runs the full audit, ranks fixes by estimated score lift, folds cross-page gaps (no sitemap, missing llms.txt, schema absent sitewide) into single items. Output: &lt;code&gt;plan.json&lt;/code&gt; + &lt;code&gt;plan.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run your agent against the plan. Each item carries a baked tool call + acceptance check, so the agent executes sequentially. Analyze-and-propose only - nothing changes until you run &lt;code&gt;apply&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Install (all three surfaces)&lt;/strong&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;# Option A - Claude Code plugin (recommended)&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/AutomateLab-tech/ai-seo-magic-button
&lt;span class="c"&gt;# Option B - MCP server in any host (Cursor, Cline, Windsurf, Claude Desktop, ...)&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; ai-seo-magic-button mcp
&lt;span class="c"&gt;# Option C - Claude skill only: copy skill/ into .claude/skills/ai-seo-magic-button/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"ai-seo-magic-button"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai-seo-magic-button"&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"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; - Ask your agent: "Run the AI SEO magic button on example.com and show me the top 5 fixes." The &lt;code&gt;generate_seo_plan&lt;/code&gt; tool returns the plan as structured content. CLI adds &lt;code&gt;audit&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt; (idempotent), &lt;code&gt;verify&lt;/code&gt; (before/after score diff), &lt;code&gt;sink&lt;/code&gt; (push items to a Notion board).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt; - Product page: &lt;a href="https://automatelab.tech/products/skills-and-plugins/ai-seo-magic-button/" rel="noopener noreferrer"&gt;https://automatelab.tech/products/skills-and-plugins/ai-seo-magic-button/&lt;/a&gt; · GitHub: &lt;a href="https://github.com/AutomateLab-tech/ai-seo-magic-button" rel="noopener noreferrer"&gt;https://github.com/AutomateLab-tech/ai-seo-magic-button&lt;/a&gt; · npm: &lt;a href="https://www.npmjs.com/package/ai-seo-magic-button" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ai-seo-magic-button&lt;/a&gt; · Plan format: &lt;a href="https://github.com/AutomateLab-tech/ai-seo-magic-button/blob/main/docs/plan-format.md" rel="noopener noreferrer"&gt;https://github.com/AutomateLab-tech/ai-seo-magic-button/blob/main/docs/plan-format.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>aeo</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How we made our site agent-ready with WebMCP (and what it bought us)</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Tue, 02 Jun 2026 10:10:41 +0000</pubDate>
      <link>https://dev.to/ratamaha/how-we-made-our-site-agent-ready-with-webmcp-and-what-it-bought-us-20p3</link>
      <guid>https://dev.to/ratamaha/how-we-made-our-site-agent-ready-with-webmcp-and-what-it-bought-us-20p3</guid>
      <description>&lt;p&gt;TL;DR: We wired every tool and search box on automatelab.tech to AI agents using WebMCP - the &lt;code&gt;navigator.modelContext&lt;/code&gt; browser API - by reusing the functions our on-page buttons already call. It now shows up in Lighthouse's new Agentic Browsing audit. Here is how, and an honest read on whether it is worth your time in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  What WebMCP is
&lt;/h2&gt;

&lt;p&gt;WebMCP is a browser API (&lt;code&gt;navigator.modelContext&lt;/code&gt;) that a page calls to register tools for an in-browser AI agent. Each tool carries a name, a natural-language description, a JSON input schema, and a handler that runs when the agent calls it. It is the browser-side cousin of the Model Context Protocol that server-side integrations already speak. The spec is a W3C draft from the Web Machine Learning group.&lt;/p&gt;

&lt;p&gt;The honest part: WebMCP ships only in Chrome 146 behind a feature flag, real-world adoption is near zero, and most agents today still read the raw DOM. This is a plant-the-flag move. Two reasons to do it anyway: it is cheap if your pages already have working actions, and Google just started measuring it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering a tool
&lt;/h2&gt;

&lt;p&gt;There are two paths. The declarative one adds &lt;code&gt;toolname&lt;/code&gt; and &lt;code&gt;tooldescription&lt;/code&gt; attributes to an existing &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; and the browser builds the input schema for you. The imperative one calls &lt;code&gt;registerTool()&lt;/code&gt;. We took the imperative path because every tool already had a compute function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audit_ai_seo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Audit a URL for AEO/GEO readiness; return the score.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full URL incl. https://&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;required&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;url&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;execute&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;url&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runAudit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// the function the page already had&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&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;p&gt;The point: you are not writing new tools, you are exposing the ones you already shipped. The whole integration is a roughly forty-line helper that feature-detects &lt;code&gt;navigator.modelContext&lt;/code&gt; and no-ops in every browser without it, so normal visitors are unaffected.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we exposed
&lt;/h2&gt;

&lt;p&gt;Ten surfaces, each reusing the function behind its on-page button:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;WebMCP tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI SEO Checker&lt;/td&gt;
&lt;td&gt;&lt;code&gt;audit_ai_seo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Overview checker&lt;/td&gt;
&lt;td&gt;&lt;code&gt;score_ai_overview_eligibility&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n8n cost calculator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;estimate_n8n_cost&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n8n workflow validator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;validate_n8n_workflow&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPTBot / robots.txt helper&lt;/td&gt;
&lt;td&gt;&lt;code&gt;get_ai_crawler_robots_rules&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blog search&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search_blog&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation Error Index search&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search_automation_errors&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Lighthouse angle
&lt;/h2&gt;

&lt;p&gt;Lighthouse 13.3 added an Agentic Browsing category. The WebMCP check is informational only - it lists the tools your page registers and never fails you for having none. The other three checks (llms.txt, accessibility-tree integrity, and layout stability) pay off today regardless of WebMCP adoption: they help screen readers, retrieval crawlers, and real users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth it in 2026?
&lt;/h2&gt;

&lt;p&gt;Do the agent-readiness groundwork for the wins you can bank now - ship &lt;code&gt;llms.txt&lt;/code&gt;, fix the accessibility tree, kill layout shift - and treat the WebMCP registration as a cheap option on a standard that might matter in a year. One caveat worth taking seriously: the WebMCP security model is still incomplete, so never expose a destructive or account-changing action without a user-confirmation step. Read-only audits, calculators, and search are safe first candidates; "delete my account" is not.&lt;/p&gt;

&lt;p&gt;Full write-up with the step-by-step: &lt;a href="https://automatelab.tech/blog/al-products/how-we-made-automatelab-tech-agent-ready-webmcp/" rel="noopener noreferrer"&gt;https://automatelab.tech/blog/al-products/how-we-made-automatelab-tech-agent-ready-webmcp/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webmcp</category>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
    </item>
    <item>
      <title>Launching the seo-performance MCP</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Thu, 28 May 2026 12:52:46 +0000</pubDate>
      <link>https://dev.to/ratamaha/launching-the-seo-performance-mcp-1d2m</link>
      <guid>https://dev.to/ratamaha/launching-the-seo-performance-mcp-1d2m</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; seo-performance-mcp pulls Google Search Console, GA4, Matomo, Clarity, and AI-citation data per URL and emits a verdict per post (refresh, expand, merge, kill, double_down, hold) with explicit reason codes.&lt;/p&gt;

&lt;p&gt;We just published &lt;code&gt;@automatelab/seo-performance-mcp&lt;/code&gt; on npm. It is a Model Context Protocol server for the part of SEO that starts after the post goes live: triage. Which URLs are decaying, which are quietly winning, which lost an AI citation last week, which sit at position 7 with a low CTR and want a title rewrite. Free, MIT-licensed, no email gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does seo-performance-mcp do?
&lt;/h2&gt;

&lt;p&gt;Most SEO tooling stops at "the post is live." This MCP starts there. For any URL on your site it pulls Search Console (clicks, impressions, CTR, position, top queries), GA4 (pageviews), Matomo (visits, dwell), Microsoft Clarity (scroll depth, rage clicks), and AI-citation counts from a configured citation-intelligence endpoint. Every signal source is optional. Provide the env vars for the platforms you use; the rest are skipped silently.&lt;/p&gt;

&lt;p&gt;The data flows through a deterministic verdict engine. It combines the snapshot with a 12-week GSC decay curve and emits one of six calls per post: &lt;code&gt;refresh&lt;/code&gt;, &lt;code&gt;expand&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, &lt;code&gt;kill&lt;/code&gt;, &lt;code&gt;double_down&lt;/code&gt;, or &lt;code&gt;hold&lt;/code&gt;. Each verdict carries explicit reason codes (the rules live in &lt;code&gt;src/verdict/rules.ts&lt;/code&gt; and can be inspected) and a 0-1 confidence score. The mapping is rules-based, not LLM-based, so the same inputs always produce the same call.&lt;/p&gt;

&lt;p&gt;Reporting only. The server never edits a post, never publishes anything, never touches a third-party platform with anything other than a read. The verdict and the refresh brief are hand-off artefacts for a writer, an editor, or a downstream rewrite tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which tools does it ship?
&lt;/h2&gt;

&lt;p&gt;Eight tools, organised as a dot-notation tree: &lt;code&gt;posts.*&lt;/code&gt; for per-URL analysis, &lt;code&gt;cohort.*&lt;/code&gt; for cross-post roll-ups, &lt;code&gt;gsc.*&lt;/code&gt; for direct Search Console scans.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;posts.list&lt;/code&gt;: Discover indexable posts via an XML sitemap, a JSON override list, or the Ghost Admin API. Filter by minimum age or published-after date.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posts.snapshot&lt;/code&gt;: Pull a 30, 60, or 90-day snapshot across every configured signal source for one URL. Each source is best-effort.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posts.decay_curve&lt;/code&gt;: Bucket GSC clicks, impressions, and average position into weekly windows and classify the trend: decay, plateau, or growth. Underpins the verdict engine's decay rules.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posts.verdict&lt;/code&gt;: Run the rule-based verdict engine on one URL. Returns verdict, reason codes, and confidence.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posts.refresh_brief&lt;/code&gt;: Produce a markdown brief for a human or downstream LLM editor: verdict, reasons, raw numbers, top queries, suggested actions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posts.cite_loss&lt;/code&gt;: Return the list of LLMs that previously cited this URL but no longer do, with the prior query and last-seen date. Optionally identifies the URL that replaced yours.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cohort.report&lt;/code&gt;: Run the verdict engine across a cohort filtered by tag and minimum age. Returns a ranked table sorted by verdict priority then confidence. Answers "which three posts should I refresh this week?"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gsc.quick_wins&lt;/code&gt;: Scan GSC for (page, query) pairs at positions 5 to 15 with non-trivial impressions and a CTR below the position-expected curve. The fastest title-rewrite wins. Platform-agnostic pure GSC pull.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three discoverable prompts ship alongside: &lt;code&gt;audit_cohort&lt;/code&gt;, &lt;code&gt;find_quick_wins&lt;/code&gt;, &lt;code&gt;citation_loss_sweep&lt;/code&gt;. No skill loader required; any MCP-capable client picks them up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Requires Node 20+. Add the server to your MCP host config (&lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; for Cursor, &lt;code&gt;claude_desktop_config.json&lt;/code&gt; for Claude Desktop, &lt;code&gt;.mcp.json&lt;/code&gt; for Claude Code), then restart the host.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
json
{
  "mcpServers": {
    "seo-performance": {
      "command": "npx",
      "args": ["-y", "@automatelab/seo-performance-mcp"],
      "env": {
        "GSC_SERVICE_ACCOUNT_JSON": "/abs/path/to/gsc-service-account.json",
        "GSC_SITE_URL": "sc-domain:example.com",
        "MATOMO_BASE_URL": "https://example.com/analytics",
        "MATOMO_TOKEN": "...",
        "MATOMO_SITE_ID": "1",
        "GA4_PROPERTY_ID": "123456789",
        "CLARITY_PROJECT_ID": "...",
        "GHOST_ADMIN_KEY": "id:secret"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>seo</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Free AI SEO Checker: 39 AEO and GEO Checks Explained</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Tue, 26 May 2026 13:49:15 +0000</pubDate>
      <link>https://dev.to/ratamaha/free-ai-seo-checker-39-aeo-and-geo-checks-explained-380l</link>
      <guid>https://dev.to/ratamaha/free-ai-seo-checker-39-aeo-and-geo-checks-explained-380l</guid>
      <description>&lt;p&gt;We just shipped a free, no-signup AI SEO checker that scores any URL across 39 checks for answer-engine optimization (AEO) and generative-engine optimization (GEO). Here's what it actually tests and why we built it.&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%2Fodf67mzli5vcue9fmyri.jpg" 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%2Fodf67mzli5vcue9fmyri.jpg" alt=" " width="799" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with existing SEO tools for AI search
&lt;/h2&gt;

&lt;p&gt;Most SEO auditors check title length, keyword density, and Core Web Vitals. That's fine for Google's traditional ranking pipeline. But AI search engines (ChatGPT, Perplexity, Claude, Gemini) extract answers differently. They care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether your robots.txt blocks their crawlers&lt;/li&gt;
&lt;li&gt;Whether your opening paragraph resolves the query in under 60 words&lt;/li&gt;
&lt;li&gt;Whether your JSON-LD schema type matches what the page actually says&lt;/li&gt;
&lt;li&gt;Whether your content renders in raw HTML (not behind JavaScript)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that shows up in Screaming Frog or Ahrefs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the 39 checks cover
&lt;/h2&gt;

&lt;p&gt;The checker runs five categories, each weighted by signal strength:&lt;/p&gt;

&lt;h3&gt;
  
  
  Crawlability (6 checks, weights 3-5)
&lt;/h3&gt;

&lt;p&gt;Checks whether GPTBot, OAI-SearchBot, ClaudeBot, Claude-SearchBot, PerplexityBot, and Amazonbot can reach your pages. A robots.txt that blocks &lt;code&gt;Googlebot&lt;/code&gt; is usually fine; one that accidentally blocks &lt;code&gt;GPTBot&lt;/code&gt; via a wildcard disallow is invisible to ChatGPT's index.&lt;/p&gt;

&lt;p&gt;Also checks robots.txt accessibility (returns 200, not 401/500), sitemap presence, and canonical URL consistency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured Data (7 checks, weights 2-4)
&lt;/h3&gt;

&lt;p&gt;Validates JSON-LD presence, schema type alignment (does &lt;code&gt;@type: Article&lt;/code&gt; match what you actually published?), required properties per schema type, OpenGraph and Twitter cards, and author attribution with Wikidata or LinkedIn links.&lt;/p&gt;

&lt;p&gt;The author attribution check matters: several citation studies show AI systems favor content where the author has a verifiable external identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  On-Page Content (11 checks, weights 1-4)
&lt;/h3&gt;

&lt;p&gt;The most nuanced category. Key checks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What triggers it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Question-style heading ratio&lt;/td&gt;
&lt;td&gt;Less than 30% of H2-H4s are question-form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading level&lt;/td&gt;
&lt;td&gt;Flesch-Kincaid grade above 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answer density&lt;/td&gt;
&lt;td&gt;Opening paragraph doesn't resolve the query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal link depth&lt;/td&gt;
&lt;td&gt;Thin internal link structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image alt text&lt;/td&gt;
&lt;td&gt;Images missing alt attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The question-heading threshold comes from a study showing 72% of ChatGPT-cited pages use question-style headings. The tool flags pages below the 30% ratio.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Readability (8 checks, weights 2-5)
&lt;/h3&gt;

&lt;p&gt;Checks answer density in the opening paragraph (first paragraph should contain the core answer in under 60 words), render mode (page must have 200+ visible words in raw HTML to be parseable by non-executing crawlers), llms.txt presence, entity recognition, authorship signals, and outbound links to authoritative sources.&lt;/p&gt;

&lt;p&gt;The render detection is important: a page that loads its content via React client-side rendering is effectively invisible to AI crawlers that don't execute JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Health (7 checks, weights 1-3)
&lt;/h3&gt;

&lt;p&gt;HTTPS, mixed content, mobile viewport meta tag, payload compression, image dimension attributes, HSTS headers, and basic security headers. These are table-stakes checks that most sites pass, but the tool catches edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoring
&lt;/h2&gt;

&lt;p&gt;Each check has a weight (1-5). The tool calculates category scores and an overall weighted letter grade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A: 90+&lt;/li&gt;
&lt;li&gt;B: 75-89&lt;/li&gt;
&lt;li&gt;C: 60-74&lt;/li&gt;
&lt;li&gt;D: 40-59&lt;/li&gt;
&lt;li&gt;F: below 40&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works technically
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;User pastes a URL&lt;/li&gt;
&lt;li&gt;A server-side proxy fetches the HTML, robots.txt, sitemap, and llms.txt in a single request (bypasses CORS restrictions; nothing is stored)&lt;/li&gt;
&lt;li&gt;All 39 checks run client-side in the browser&lt;/li&gt;
&lt;li&gt;Results render immediately with evidence strings and remediation guidance per check&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No login. No data stored. One URL per run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why free and no-signup
&lt;/h2&gt;

&lt;p&gt;We want this to be the first tool developers and content teams reach for when they're wondering why their content isn't appearing in AI answers. The friction of a signup form is the wrong trade-off here. The &lt;code&gt;@automatelab/ai-seo-mcp&lt;/code&gt; package is the commercial path for teams that need bulk audits inside Claude Code or Cursor workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://automatelab.tech/blog/al-products/free-ai-seo-checker-aeo-geo/" rel="noopener noreferrer"&gt;https://automatelab.tech/blog/al-products/free-ai-seo-checker-aeo-geo/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tool is at the link above. If you find a check that's giving a false positive or a signal that should be weighted differently, open an issue or drop a comment.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of the &lt;a href="https://automatelab.tech" rel="noopener noreferrer"&gt;AutomateLab&lt;/a&gt; AI automation stack. Also see &lt;a href="https://automatelab.tech/tools/ai-seo-mcp/" rel="noopener noreferrer"&gt;&lt;code&gt;@automatelab/ai-seo-mcp&lt;/code&gt;&lt;/a&gt; for Claude Code batch audits.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiseo</category>
      <category>aeo</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>We were about to pay $400/month for an AI citation dashboard, so we built one</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Mon, 25 May 2026 13:52:55 +0000</pubDate>
      <link>https://dev.to/ratamaha/we-were-about-to-pay-400month-for-an-ai-citation-dashboard-so-we-built-one-jgh</link>
      <guid>https://dev.to/ratamaha/we-were-about-to-pay-400month-for-an-ai-citation-dashboard-so-we-built-one-jgh</guid>
      <description>&lt;p&gt;Hosted AI citation dashboards (Profound, AthenaHQ, Otterly, Ahrefs Brand Radar) start at $295 to $499 per month. They tell you which URLs ChatGPT, Claude, Perplexity, Gemini, Google AI Overviews, and Bing cite for any query.&lt;/p&gt;

&lt;p&gt;That data is mostly available through each vendor's own API. The dashboard is the product; the data is commodity. So we built Citation Intelligence MCP - a free, self-hosted MCP server that does the same job, with 12 tools, no backend, and per-query costs measured in cents.&lt;/p&gt;

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

&lt;p&gt;12 tools across three jobs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Track citations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;check_citations&lt;/code&gt;, &lt;code&gt;am_i_cited&lt;/code&gt;, &lt;code&gt;ai_overview&lt;/code&gt;, &lt;code&gt;cited_for&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predict citations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;predict_citation&lt;/code&gt;, &lt;code&gt;compare_domains&lt;/code&gt;, &lt;code&gt;wikipedia_mentions&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find leaks&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gsc_citation_gap&lt;/code&gt;, &lt;code&gt;audit_sitemap&lt;/code&gt;, &lt;code&gt;citation_trend&lt;/code&gt;, &lt;code&gt;run_panel&lt;/code&gt;, &lt;code&gt;track_queries&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three of the tools (&lt;code&gt;predict_citation&lt;/code&gt;, &lt;code&gt;cited_for&lt;/code&gt;, &lt;code&gt;wikipedia_mentions&lt;/code&gt;) run on a local cache and cost zero. The rest use your own keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @automatelab/citation-intelligence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire into Claude Code by adding to &lt;code&gt;.mcp.json&lt;/code&gt;:&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;"citation-intelligence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@automatelab/citation-intelligence"&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;Bring your own keys for the paid tools:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SERPAPI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...   &lt;span class="c"&gt;# for Google AI Overviews&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost is roughly $0.01 to $0.03 per query at premium engines, so a daily 50-query panel runs you about $20/month vs $400 for the hosted equivalent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The report that made us actually ship this
&lt;/h2&gt;

&lt;p&gt;The one that got us is &lt;code&gt;gsc_citation_gap&lt;/code&gt;. It joins Google Search Console data with AI citation status and returns pages where you rank in Google but get zero AI mentions. The gap is the editorial budget.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gsc_citation_gap site:automatelab.tech &lt;span class="nt"&gt;--threshold-impressions&lt;/span&gt; 1000
&lt;span class="go"&gt;
URL                                          GSC impressions   Cited by
/glossary/claude-code/                                 4,238   0 engines
/glossary/llms-txt/                                    3,102   1 engine (Perplexity)
/glossary/json-ld/                                     2,847   0 engines
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything cited by zero engines and ranking on Google is a page that needs FAQ markup, query-shaped H2s, or a direct rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MCP-first
&lt;/h2&gt;

&lt;p&gt;We did not want a dashboard. We wanted citation data inside the same agent loop that writes the post. An MCP server lives next to your editorial flow - the agent that drafts your next article can call &lt;code&gt;predict_citation&lt;/code&gt; before writing the H2, then &lt;code&gt;am_i_cited&lt;/code&gt; a week after publish.&lt;/p&gt;

&lt;p&gt;The full launch write-up: &lt;a href="https://automatelab.tech/launching-the-citation-intelligence-mcp/" rel="noopener noreferrer"&gt;https://automatelab.tech/launching-the-citation-intelligence-mcp/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/automatelab/citation-intelligence" rel="noopener noreferrer"&gt;https://github.com/automatelab/citation-intelligence&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. No backend, no telemetry, no account.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>automation</category>
      <category>citation</category>
    </item>
    <item>
      <title>I built an open dataset of 1,119 SaaS webhook events. Here's what surprised me.</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Wed, 20 May 2026 09:34:19 +0000</pubDate>
      <link>https://dev.to/ratamaha/i-built-an-open-dataset-of-1119-saas-webhook-events-heres-what-surprised-me-120l</link>
      <guid>https://dev.to/ratamaha/i-built-an-open-dataset-of-1119-saas-webhook-events-heres-what-surprised-me-120l</guid>
      <description>&lt;p&gt;If you have ever tried to wire up webhooks from more than three SaaS apps, you already know the punchline: every vendor invented their own conventions, and none of them are wrong, but none of them agree either.&lt;/p&gt;

&lt;p&gt;I was building agent tooling that had to understand many of them. Halfway through, I stopped trying to keep it in my head and started writing it down. Then I kept writing it down. The result is now an open dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1,119 webhook events. 30 platforms. One schema. Free, CC-BY-4.0.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HuggingFace: &lt;a href="https://huggingface.co/datasets/automatelab/saas-webhooks" rel="noopener noreferrer"&gt;automatelab/saas-webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browsable index: &lt;a href="https://automatelab.tech/products/datasets/saas-webhooks/" rel="noopener noreferrer"&gt;automatelab.tech/products/datasets/saas-webhooks/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;A normalized catalog covering Stripe, GitHub, Slack, Notion, Linear, Jira, HubSpot, Salesforce, Zendesk, Intercom, Discord, Twilio, Calendly, Mailchimp, Zoom, Microsoft Teams, PagerDuty, Pipedrive, Asana, ClickUp, Front, Help Scout, Loom, Greenhouse, Ashby, BambooHR, Gusto, Attio, Close, and Freshdesk.&lt;/p&gt;

&lt;p&gt;For every event, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;event_name&lt;/code&gt; and &lt;code&gt;trigger_description&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;payload_schema&lt;/code&gt; as JSON Schema (draft 2020-12)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;auth_method&lt;/code&gt;, &lt;code&gt;signature_header&lt;/code&gt;, and the exact signing algorithm&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;delivery_guarantees&lt;/code&gt; and &lt;code&gt;retry_policy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;idempotency_key_header&lt;/code&gt; (if the vendor provides one)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs_url&lt;/code&gt; back to the canonical vendor docs&lt;/li&gt;
&lt;li&gt;Format: JSONL per vendor, plus Parquet for the full set&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A sample row
&lt;/h2&gt;

&lt;p&gt;Here is what a single Stripe event looks like, trimmed:&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;"vendor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stripe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_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;"account.application.authorized"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trigger_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fires when a user authorizes a Stripe application."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hmac-sha256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signature_header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stripe-Signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signature_algorithm_detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HMAC-SHA256 with versioned scheme; header contains timestamp and v1 hash. Verify timestamp to prevent replay attacks."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"delivery_guarantees"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"at-least-once"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry_policy"&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;"max_attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"backoff"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Exponential backoff over multiple hours."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_retry_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PT72H"&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;"payload_schema"&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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://json-schema.org/draft/2020-12/schema"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"..."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"docs_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.stripe.com/api/events/types"&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;Same shape across all 30 vendors. That is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The surprising stuff
&lt;/h2&gt;

&lt;p&gt;A few things jumped out once everything was in one schema:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signing is not standardized in any way.&lt;/strong&gt; A small sampler:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;HMAC-SHA256&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Stripe-Signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp + v1 hash, replay-window enforced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;HMAC-SHA256&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Hub-Signature-256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plus legacy SHA1 on &lt;code&gt;X-Hub-Signature&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;HMAC-SHA256&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Slack-Signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5-minute timestamp window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shopify&lt;/td&gt;
&lt;td&gt;HMAC-SHA256&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Shopify-Hmac-Sha256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base64 of HMAC of raw body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;HMAC-SHA256&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Linear-Signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hex digest only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same algorithm, five different envelopes. Anyone writing one verifier and trying to reuse it has a bad afternoon ahead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retry policies are wildly different.&lt;/strong&gt; Stripe retries for 72 hours with exponential backoff. GitHub does not retry by default at all (it depends on app type). Slack retries 3 times. Some vendors do not publish a retry policy in their docs, which means you should not rely on one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency support is hit-or-miss.&lt;/strong&gt; GitHub gives you &lt;code&gt;X-GitHub-Delivery&lt;/code&gt;. Stripe gives you the event &lt;code&gt;id&lt;/code&gt;. Several vendors give you nothing, which means you either dedupe by payload hash or accept duplicates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Max payload sizes are mostly undocumented.&lt;/strong&gt; GitHub publishes 25 MB. Most vendors do not say.&lt;/p&gt;

&lt;p&gt;These are not opinions. They are facts I would rather not have had to learn the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this exists
&lt;/h2&gt;

&lt;p&gt;I am building agents that integrate with many SaaS products. Two things have to be true for an agent to call any webhook tool correctly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent needs the payload schema to know what fields it can rely on.&lt;/li&gt;
&lt;li&gt;The agent needs the auth contract to know how to validate inbound deliveries.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If that information is scattered across 30 different docs sites in 30 different shapes, the agent cannot use it. Once it is in a single schema, the agent can.&lt;/p&gt;

&lt;p&gt;Same logic applies to anything that has to interop with many vendors at once: an integration platform, a security scanner that audits webhook configurations, a docs site that needs to render comparison tables, a research notebook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use it however
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dataset&lt;/span&gt;

&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;automatelab/saas-webhooks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;stripe_events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;train&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vendor&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stripe&lt;/span&gt;&lt;span class="sh"&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="n"&gt;stripe_events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payload_schema&lt;/span&gt;&lt;span class="sh"&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 dataset is CC-BY-4.0. No email gate, no sign-up. Attribution is appreciated when you ship something interesting on top of it.&lt;/p&gt;

&lt;p&gt;It updates monthly from source. If you find a missing event or a vendor that should be there, the GitHub issue tracker is the right place to drop it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dataset on HuggingFace: &lt;a href="https://huggingface.co/datasets/automatelab/saas-webhooks" rel="noopener noreferrer"&gt;automatelab/saas-webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browsable catalog (one page per vendor): &lt;a href="https://automatelab.tech/products/datasets/saas-webhooks/" rel="noopener noreferrer"&gt;automatelab.tech/products/datasets/saas-webhooks/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;All open datasets: &lt;a href="https://automatelab.tech/products/datasets/" rel="noopener noreferrer"&gt;automatelab.tech/products/datasets/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build something on top of it I would genuinely like to know. The whole point of putting it under CC-BY is that the work compounds.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>ai</category>
      <category>opensource</category>
      <category>dataset</category>
    </item>
    <item>
      <title>Launching content-distribution-mcp: one finished post, eight channels</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Tue, 19 May 2026 20:00:34 +0000</pubDate>
      <link>https://dev.to/ratamaha/launching-content-distribution-mcp-one-finished-post-eight-channels-2eme</link>
      <guid>https://dev.to/ratamaha/launching-content-distribution-mcp-one-finished-post-eight-channels-2eme</guid>
      <description>&lt;p&gt;We just shipped &lt;a href="https://github.com/automatelab-tech/content-distribution-mcp" rel="noopener noreferrer"&gt;content-distribution-mcp&lt;/a&gt;: an MCP server that takes one finished post and routes it to eight developer-facing channels.&lt;/p&gt;

&lt;p&gt;It makes zero LLM calls. Adapter I/O only. Whatever model your MCP host runs is the model that writes the per-channel copy — Claude, GPT-4, Gemini, a local Llama, or none at all if you bring your own text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;content-distribution-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why we built this
&lt;/h2&gt;

&lt;p&gt;Posting one blog article to DEV.to, Hashnode, GitHub Discussions, Bluesky, Reddit, LinkedIn, Twitter, and Medium is roughly 90 minutes of paste-and-fiddle per release. We were doing that by hand. So we wrote an MCP that handles the I/O: auth, idempotency, scheduling, retries, the Reddit anti-spam gate, and the manual-channel browser pre-fill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box
&lt;/h2&gt;

&lt;p&gt;Eight MCP tools — and that is the full surface:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;publish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Immediate publish; idempotent on &lt;code&gt;(content.id, variant.channel)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;schedule&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Queue variants for a future &lt;code&gt;schedule_at&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;drain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fire any due scheduled posts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-variant state for a content piece&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unpublish&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Best-effort delete (DEV.to / GitHub Discussions only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Static per-channel metadata (char limits, tags, canonical-URL support)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_profiles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Configured Distribution Profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_subreddits&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Curated Subreddit Catalog entries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Eight channels, three tiers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DEV.to&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Forem API, native &lt;code&gt;canonical_url&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hashnode&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;GraphQL, native &lt;code&gt;originalArticleURL&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Discussions&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Per-repo GraphQL, footer for canonical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bluesky&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;atproto SDK, canonical appended to post text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reddit&lt;/td&gt;
&lt;td&gt;Auto-gated&lt;/td&gt;
&lt;td&gt;Per-subreddit cooldown, 5/day global cap, self-promo ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Playwright pre-fill + mark-live CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinkedIn&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Personal + company-admin compose, plain-text draft&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twitter / X&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Free-tier API unusable; compose URL + plain-text draft&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The three manual channels exist because their APIs are either gone (Twitter free tier), paid-tier (LinkedIn Marketing API), or never existed (Medium). The MCP opens a pre-filled compose tab and the operator clicks Submit. Less elegant, still beats the manual copy/paste workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reddit gate
&lt;/h2&gt;

&lt;p&gt;Reddit punishes anyone who treats it like a content pipe. The adapter enforces four rules before posting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Per-subreddit cooldown — the last live post-log entry for that sub&lt;/li&gt;
&lt;li&gt;5-per-day global cap — over the operator's full Reddit history with this MCP&lt;/li&gt;
&lt;li&gt;Self-promo ratio — over the last 30 posts in that subreddit, your own-domain links must stay under the curated &lt;code&gt;max_self_link_pct&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Account age + karma — against minimums in the curated Subreddit Catalog&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any rule fails, &lt;code&gt;publish&lt;/code&gt; returns &lt;code&gt;state=failed&lt;/code&gt; with the specific reason. No &lt;code&gt;bypass&lt;/code&gt; flag. If you want to post anyway, write a new entry in the Subreddit Catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  YAML or Notion: pick one
&lt;/h2&gt;

&lt;p&gt;Two backends implement the same &lt;code&gt;StateBackend&lt;/code&gt; Protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;YamlBackend&lt;/code&gt; — four YAML files under &lt;code&gt;~/.distribution-mcp/&lt;/code&gt;. Zero config; right for solo/local use.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NotionBackend&lt;/code&gt; — three Notion databases (Distribution Profiles, Subreddit Catalog, Post Log). Right for team/agency use; operators can audit the queue in Notion's UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You swap them with a single constructor argument. No caller code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Works with any MCP host
&lt;/h2&gt;

&lt;p&gt;The server speaks standard MCP (stdio or SSE) and has zero Anthropic-specific code:&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="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-ri&lt;/span&gt; &lt;span class="s2"&gt;"anthropic"&lt;/span&gt; src/  &lt;span class="c"&gt;# returns nothing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tested with Claude Code, Cursor, n8n's MCP Client node, and the plain &lt;code&gt;mcp&lt;/code&gt; Python client. If your host can talk MCP, it can talk to this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;content-distribution-mcp

&lt;span class="c"&gt;# optional extras&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"content-distribution-mcp[browser]"&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"content-distribution-mcp[bluesky]"&lt;/span&gt;
playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium  &lt;span class="c"&gt;# only if you use the browser channels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then wire into your MCP host config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .claude/mcp.json&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;"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;"content-distribution"&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;"content-distribution-mcp"&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;"serve"&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;h2&gt;
  
  
  How we use it ourselves
&lt;/h2&gt;

&lt;p&gt;This very post is the dogfood. The Ghost blog post is the canonical: &lt;a href="https://automatelab.tech/content-distribution-mcp/" rel="noopener noreferrer"&gt;https://automatelab.tech/content-distribution-mcp/&lt;/a&gt;. The DEV.to article you're reading came out of the same &lt;code&gt;publish&lt;/code&gt; tool with a &lt;code&gt;devto:main&lt;/code&gt; variant. Bluesky was the second auto-channel. The browser-tier channels (LinkedIn / Twitter / Reddit) got their compose tabs pre-filled by the same MCP run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo — &lt;a href="https://github.com/automatelab-tech/content-distribution-mcp" rel="noopener noreferrer"&gt;github.com/automatelab-tech/content-distribution-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Landing — &lt;a href="https://automatelab.tech/products/mcp/content-distribution-mcp/" rel="noopener noreferrer"&gt;automatelab.tech/products/mcp/content-distribution-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full blog post — &lt;a href="https://automatelab.tech/content-distribution-mcp/" rel="noopener noreferrer"&gt;automatelab.tech/content-distribution-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT licensed. Issues, PRs, or any "I tried it and X broke" notes are welcome.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>python</category>
      <category>opensource</category>
      <category>automation</category>
    </item>
    <item>
      <title>What I learned introspecting 922 npm MCP servers</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Tue, 19 May 2026 14:20:28 +0000</pubDate>
      <link>https://dev.to/ratamaha/what-i-learned-introspecting-922-npm-mcp-servers-4a14</link>
      <guid>https://dev.to/ratamaha/what-i-learned-introspecting-922-npm-mcp-servers-4a14</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We ran &lt;code&gt;npx -y &amp;lt;package&amp;gt;&lt;/code&gt; against 922 npm-published MCP servers, sent them the JSON-RPC &lt;code&gt;initialize&lt;/code&gt; and &lt;code&gt;tools/list&lt;/code&gt; calls, and captured what they did. 359 responded. 563 failed in 15 distinct ways that say more about npm packaging than about MCP itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stderr signature that broke 261 servers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[stderr] connecting to upstream...
[stderr] (no further output)
[timeout after 120s]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the single most common failure mode in the npm MCP ecosystem right now. The process spawns, the package loads, the constructor runs, and then the server tries to phone home to its upstream API before answering the protocol. The introspection runner waits 120 seconds and gives up. Two hundred sixty-one servers - 28% of everything published - never made it past their own startup.&lt;/p&gt;

&lt;p&gt;If you maintain an MCP server and you connect to anything external during &lt;code&gt;initialize&lt;/code&gt;, you are in this bucket. The fix is to defer the upstream connection until the first tool call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually ran
&lt;/h2&gt;

&lt;p&gt;The protocol gives you a discovery method: &lt;code&gt;tools/list&lt;/code&gt;. Send it after &lt;code&gt;initialize&lt;/code&gt; and the server returns the full JSON Schema for every tool it exposes. No README scraping, no LLM interpretation, no guessing. It is the exact same thing your MCP client does when it connects.&lt;/p&gt;

&lt;p&gt;The runner does this per package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. spawn:  npx -y &amp;lt;package&amp;gt; (over stdio)
2. write:  {"jsonrpc":"2.0","id":1,"method":"initialize",
            "params":{"protocolVersion":"2024-11-05",
                      "capabilities":{},
                      "clientInfo":{"name":"introspector","version":"1.0"}}}
3. read:   initialize response
4. write:  {"jsonrpc":"2.0","method":"notifications/initialized"}
5. write:  {"jsonrpc":"2.0","id":2,"method":"tools/list"}
6. read:   tool list
7. kill:   close stdin, SIGTERM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Concurrency was 8 in parallel, 120-second timeout per server, total wall time around 25 minutes for 922 packages. The output is one JSONL row per server with status, tool count, and (where available) the full input schema for every tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 15 failure buckets
&lt;/h2&gt;

&lt;p&gt;Every server that didn't return a clean tool array was classified by inspecting the exit code, stderr, and the JSON-RPC error if there was one. The breakdown:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ok&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;359&lt;/td&gt;
&lt;td&gt;clean &lt;code&gt;tools/list&lt;/code&gt; response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;261&lt;/td&gt;
&lt;td&gt;spawned, never answered &lt;code&gt;initialize&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm_install_generic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;172&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npx -y&lt;/code&gt; itself failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_cli_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;exited with usage error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_env_var&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;generic missing env var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;broken_install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;malformed package, bad &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;bin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;unclassified crash with non-zero exit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_setup_step&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;required a CLI setup wizard run first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_slack_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;refused without &lt;code&gt;SLACK_BOT_TOKEN&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_azure_creds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;refused without Azure auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tools_list_timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;answered &lt;code&gt;initialize&lt;/code&gt;, hung on &lt;code&gt;tools/list&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_google_creds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;refused without Google auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_stripe_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;refused without &lt;code&gt;STRIPE_API_KEY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_config_file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;refused without a config path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_external_runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;shelled out to a binary that was not installed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;needs_openai_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;refused without &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The credential-wall buckets (everything &lt;code&gt;needs_*&lt;/code&gt;) add up to 109 servers, almost 12% of the published set. If you are populating an agent's tool list by parsing READMEs, those servers register as 0-tool servers in your index. They are not 0-tool servers. They are 5-tool, 12-tool, 40-tool servers waiting for a key you didn't supply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Windows-specific gotchas
&lt;/h2&gt;

&lt;p&gt;The runner is on Windows. Two things bite hard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spawning npx.&lt;/strong&gt; &lt;code&gt;npx&lt;/code&gt; on Windows resolves to &lt;code&gt;npx.cmd&lt;/code&gt;, a batch script. Node's &lt;code&gt;child_process.spawn&lt;/code&gt; without a shell will not invoke a &lt;code&gt;.cmd&lt;/code&gt;. The result is a flat &lt;code&gt;ENOENT&lt;/code&gt; even though &lt;code&gt;where npx&lt;/code&gt; shows it on PATH. The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cmd&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;/c&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;npx&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;-y&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stdio&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;pipe&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;pipe&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;pipe&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;You see this same pattern in every &lt;code&gt;claude_desktop_config.json&lt;/code&gt; on Windows. If you are writing your own MCP client, you need it too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UTF-8 stdio.&lt;/strong&gt; The default code page on Windows is not UTF-8. If an MCP server writes a tool description that contains a non-ASCII character (German umlaut, em-dash, curly quote, anything), and you read its stdio without forcing UTF-8 decoding, you get a JSON parse error halfway through &lt;code&gt;tools/list&lt;/code&gt;. Force it:&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="n"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three servers in our run had tool descriptions with non-ASCII characters. Without the encoding flag, all three would have been miscounted as &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this says to MCP server authors
&lt;/h2&gt;

&lt;p&gt;A few patterns from the data, addressed to anyone shipping a server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't connect upstream during &lt;code&gt;initialize&lt;/code&gt;.&lt;/strong&gt; If your server contacts an API on startup, it will fail introspection and it will fail any client that wants to enumerate tools without first burning a credential. Lazy-connect on the first tool call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't require CLI args to print your tool list.&lt;/strong&gt; 54 servers exit with a usage error before they will even tell you what they expose. If you need a config path, take it from an env var or accept &lt;code&gt;tools/list&lt;/code&gt; without it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document the env vars in the README.&lt;/strong&gt; The classifier successfully named the credential for the servers that wrote a clear "missing X" line to stderr. The ones that didn't ended up in the generic &lt;code&gt;needs_env_var&lt;/code&gt; bucket. That bucket is your bug report queue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ship a real &lt;code&gt;bin&lt;/code&gt; entry.&lt;/strong&gt; 11 servers in &lt;code&gt;broken_install&lt;/code&gt; had &lt;code&gt;package.json&lt;/code&gt; that pointed at a file that doesn't exist, or a &lt;code&gt;main&lt;/code&gt; field that crashed on require. None of them needed credentials. They needed a publish-time smoke test.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The full dataset
&lt;/h2&gt;

&lt;p&gt;The 9,922 tool schemas and the 922 server status rows are on HuggingFace as &lt;a href="https://huggingface.co/datasets/automatelab/mcp-servers-tool-catalog" rel="noopener noreferrer"&gt;automatelab/mcp-servers-tool-catalog&lt;/a&gt; under CC-BY-4.0. Pipeline source is at &lt;a href="https://github.com/AutomateLab-tech/mcp-tool-catalog" rel="noopener noreferrer"&gt;AutomateLab-tech/mcp-tool-catalog&lt;/a&gt;, and it re-runs on the 1st of every month via GitHub Actions.&lt;br&gt;
Product page: &lt;a href="https://automatelab.tech/products/datasets/mcp-tool-catalog/" rel="noopener noreferrer"&gt;https://automatelab.tech/products/datasets/mcp-tool-catalog/&lt;/a&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dataset&lt;/span&gt;

&lt;span class="n"&gt;servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;automatelab/mcp-servers-tool-catalog&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;servers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;train&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Just the unreachable ones, grouped by failure mode
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;servers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your server is in a failure bucket and shouldn't be, open a PR on the pipeline repo. The next monthly run picks it up.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>Here is how I use Agency-OS to interact with my agents on the next level</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Tue, 19 May 2026 10:25:40 +0000</pubDate>
      <link>https://dev.to/ratamaha/here-is-how-i-use-agency-os-to-interact-with-my-agents-on-the-next-level-1fgp</link>
      <guid>https://dev.to/ratamaha/here-is-how-i-use-agency-os-to-interact-with-my-agents-on-the-next-level-1fgp</guid>
      <description>&lt;p&gt;One thing I realized after working deeply with AI agents:&lt;/p&gt;

&lt;p&gt;Execution is not the bottleneck anymore. Structure is.&lt;/p&gt;

&lt;p&gt;You start with a simple task like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Create a product for AI SEO.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then your agent starts suggesting things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP integration
&lt;/li&gt;
&lt;li&gt;repo indexing
&lt;/li&gt;
&lt;li&gt;semantic search
&lt;/li&gt;
&lt;li&gt;auto-generated landing pages
&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every feature creates another layer of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;research
&lt;/li&gt;
&lt;li&gt;planning
&lt;/li&gt;
&lt;li&gt;implementation
&lt;/li&gt;
&lt;li&gt;edge cases
&lt;/li&gt;
&lt;li&gt;dependencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Very quickly you end up with a huge tree of subtasks that becomes impossible to hold in your head.&lt;/p&gt;

&lt;p&gt;This is exactly where I started using Agency-OS differently.&lt;/p&gt;

&lt;p&gt;Instead of treating agents like chatbots, I treat them like autonomous operators inside a structured operating system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example workflow
&lt;/h2&gt;

&lt;p&gt;Main agent defines the product direction.&lt;/p&gt;

&lt;p&gt;Separate agents branch into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;competitor research
&lt;/li&gt;
&lt;li&gt;distribution
&lt;/li&gt;
&lt;li&gt;architecture
&lt;/li&gt;
&lt;li&gt;onboarding UX
&lt;/li&gt;
&lt;li&gt;monetization
&lt;/li&gt;
&lt;li&gt;SEO
&lt;/li&gt;
&lt;li&gt;repo structure
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every branch becomes its own structured workspace with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;goals
&lt;/li&gt;
&lt;li&gt;outcomes
&lt;/li&gt;
&lt;li&gt;decisions
&lt;/li&gt;
&lt;li&gt;context
&lt;/li&gt;
&lt;li&gt;dependencies
&lt;/li&gt;
&lt;li&gt;next actions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I can actually see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what agents are doing
&lt;/li&gt;
&lt;li&gt;why they suggest something
&lt;/li&gt;
&lt;li&gt;which tasks are blocked
&lt;/li&gt;
&lt;li&gt;where complexity explodes
&lt;/li&gt;
&lt;li&gt;which ideas are worth pursuing
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is not “more AI”.&lt;/p&gt;

&lt;p&gt;It’s visibility and orchestration.&lt;br&gt;
Without structure, agents generate chaos faster than humans.&lt;br&gt;
With structure, they become leverage.&lt;/p&gt;

&lt;p&gt;More details and examples cases: &lt;a href="https://automatelab.tech/agency-os-launch/" rel="noopener noreferrer"&gt;https://automatelab.tech/agency-os-launch/&lt;/a&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/AutomateLab-tech/agency-os" rel="noopener noreferrer"&gt;https://github.com/AutomateLab-tech/agency-os&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The image below shows how my real production Agency-OS board looks internally&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%2Fkd1111jj2w0cwn65028l.jpg" 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%2Fkd1111jj2w0cwn65028l.jpg" alt=" " width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>agents</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I turned every n8n node into a machine-readable dataset (524 nodes, free) so agents can build workflows</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Mon, 18 May 2026 12:53:00 +0000</pubDate>
      <link>https://dev.to/ratamaha/i-turned-every-n8n-node-into-a-machine-readable-dataset-524-nodes-free-so-agents-can-build-4gbf</link>
      <guid>https://dev.to/ratamaha/i-turned-every-n8n-node-into-a-machine-readable-dataset-524-nodes-free-so-agents-can-build-4gbf</guid>
      <description>&lt;p&gt;I have been writing agents that build n8n workflows. The hard part is not "call the n8n API and post a workflow JSON." The hard part is "pick the right node, with the right operation, with the right parameters, without hallucinating fields that do not exist."&lt;/p&gt;

&lt;p&gt;The n8n GUI is the source of truth. The TypeScript source files are the second source of truth. Neither is a thing you can hand to an LLM at inference time.&lt;/p&gt;

&lt;p&gt;So I extracted everything into one structured catalog and put it on HuggingFace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;524 nodes. Every operation. Every credential type. Properties schema. Free. CC-BY-4.0.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HuggingFace: &lt;a href="https://huggingface.co/datasets/automatelab/n8n-nodes-catalog" rel="noopener noreferrer"&gt;automatelab/n8n-nodes-catalog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browsable index: &lt;a href="https://automatelab.tech/products/datasets/n8n-nodes-catalog/" rel="noopener noreferrer"&gt;automatelab.tech/products/datasets/n8n-nodes-catalog/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a catalog and not "just scrape n8n.io"
&lt;/h2&gt;

&lt;p&gt;Three real problems with leaving this implicit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination cost is high.&lt;/strong&gt; An LLM that invents a &lt;code&gt;slack.sendDM&lt;/code&gt; operation will produce a workflow that imports fine and fails at runtime. Hard to detect, expensive to debug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context window pressure.&lt;/strong&gt; Dropping the entire n8n source tree into a prompt is not realistic. You want a compact index the agent can search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage is non-obvious.&lt;/strong&gt; There are two source packages (&lt;code&gt;nodes-base&lt;/code&gt; and &lt;code&gt;@n8n/nodes-langchain&lt;/code&gt;), and the split between them is not visible in the UI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The catalog flattens all of that into one row per node.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is in each row
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;node_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal id (e.g. &lt;code&gt;slack&lt;/code&gt;, &lt;code&gt;airtable&lt;/code&gt;, &lt;code&gt;lmChatOpenAi&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;display_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UI label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;categories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Top-level categories (Communication, AI, Data and Storage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subcategories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Leaf taxonomy values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;group&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;input&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;, or &lt;code&gt;transform&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Default version for multi-version nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One-liner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;credentials_required&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Credential type names (e.g. &lt;code&gt;slackApi&lt;/code&gt;, &lt;code&gt;openAiApi&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;operations_supported&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Operation values for the node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;properties_schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON describing top-level property descriptors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source_package&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;nodes-base&lt;/code&gt; or &lt;code&gt;@n8n&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source_file_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Repo-relative path to the &lt;code&gt;.node.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;github_permalink&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pinned GitHub link to the source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Format: JSON and Parquet (Snappy). License: CC-BY-4.0. Updates monthly.&lt;/p&gt;

&lt;h2&gt;
  
  
  A sample row
&lt;/h2&gt;

&lt;p&gt;Here is the Slack node, trimmed:&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;"node_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;"slack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display_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;"Slack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"categories"&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;"Communication"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"group"&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;"transform"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Send and read messages, manage channels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credentials_required"&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;"slackApi"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operations_supported"&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;"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;"channel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reaction"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties_schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;resource&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;options&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;operation&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;options&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"source_package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodes-base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"github_permalink"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/n8n-io/n8n/blob/stable/packages/nodes-base/nodes/Slack/Slack.node.ts"&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;And an AI node, to show the cross-package coverage:&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;"node_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;"lmChatOpenAi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display_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;"OpenAI Chat Model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"categories"&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;"AI"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subcategories"&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;"Language Models"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chat Models (Recommended)"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"group"&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;"transform"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credentials_required"&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;"openAiApi"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@n8n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"packages/@n8n/nodes-langchain/nodes/llms/LMChatOpenAi/LmChatOpenAi.node.ts"&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;h2&gt;
  
  
  Numbers I did not expect
&lt;/h2&gt;

&lt;p&gt;A few things that fell out of the catalog once it existed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;431 nodes from &lt;code&gt;nodes-base&lt;/code&gt;, 93 from &lt;code&gt;@n8n/nodes-langchain&lt;/code&gt;.&lt;/strong&gt; The langchain side is a real and growing chunk.&lt;/li&gt;
&lt;li&gt;The single most common credential type, by a wide margin, is &lt;code&gt;httpBasicAuth&lt;/code&gt; (because the generic HTTP Request node is everywhere). After that the long tail starts immediately.&lt;/li&gt;
&lt;li&gt;A non-trivial number of nodes have an empty &lt;code&gt;operations_supported&lt;/code&gt; list. Those are usually root nodes (LLMs, vector stores, output parsers) where the "operation" abstraction does not apply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful to know if you are writing a planner that filters by operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How agents actually use it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dataset&lt;/span&gt;

&lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;automatelab/n8n-nodes-catalog&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;train&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Filter to nodes that can post messages somewhere
&lt;/span&gt;&lt;span class="n"&gt;messaging&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;operations_supported&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messaging&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="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credentials_required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Embed every row (description, operations, credentials) into a vector store.&lt;/li&gt;
&lt;li&gt;At plan time, retrieve the top N nodes for a user request.&lt;/li&gt;
&lt;li&gt;Hand the agent only those rows. Compact context, no hallucinated operations.&lt;/li&gt;
&lt;li&gt;The agent emits an n8n workflow JSON. Validation against &lt;code&gt;properties_schema&lt;/code&gt; catches malformed configs before deploy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the same shape as RAG over a tool catalog, which is becoming a pattern in its own right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The properties schema is a top-level summary, not the full recursive parameter tree. For deep parameter shapes the &lt;code&gt;github_permalink&lt;/code&gt; is your friend.&lt;/li&gt;
&lt;li&gt;Multi-version nodes only report the default version. If you need every version of a node, the source link covers it.&lt;/li&gt;
&lt;li&gt;License is CC-BY-4.0 on the catalog additions; the n8n source itself is governed by n8n's own license, which you should respect when you ship.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dataset: &lt;a href="https://huggingface.co/datasets/automatelab/n8n-nodes-catalog" rel="noopener noreferrer"&gt;automatelab/n8n-nodes-catalog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browsable index: &lt;a href="https://automatelab.tech/products/datasets/n8n-nodes-catalog/" rel="noopener noreferrer"&gt;automatelab.tech/products/datasets/n8n-nodes-catalog/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;All open datasets: &lt;a href="https://automatelab.tech/products/datasets/" rel="noopener noreferrer"&gt;automatelab.tech/products/datasets/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build agent tooling on top of this, the thing I would most like to see is an open eval set: prompts in, expected n8n workflow JSON out. That is the next obvious missing piece, and I do not think anyone has shipped one yet.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building an MCP server for AI-SEO</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Sat, 16 May 2026 11:20:56 +0000</pubDate>
      <link>https://dev.to/ratamaha/building-an-mcp-server-for-ai-seo-533k</link>
      <guid>https://dev.to/ratamaha/building-an-mcp-server-for-ai-seo-533k</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;@automatelab/ai-seo-mcp is a 13-tool MCP server that runs AI-SEO analysis, citation scoring, and structured-data audits directly inside Claude or any MCP-compatible client.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is
&lt;/h2&gt;

&lt;p&gt;AI search engines (Perplexity, ChatGPT, Google AI Overviews) pull answers from a small set of sources. Standard SEO tools tell you about backlinks and keyword density - they do not tell you whether a page is structured for AI citation. This MCP server fills that gap: it exposes audit, scoring, rewriting, and crawlability tools that work within your existing Claude context, no browser extension or SaaS dashboard required.&lt;/p&gt;

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

&lt;p&gt;The tooling for AI-SEO was fragmented. You would need one tool to check robots.txt, another to generate llms.txt, another to score citation worthiness - and none of them integrated with the agent you are already using for content work. Claude has no native way to check whether a page will get cited. Wiring it up via MCP was the obvious fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool surface
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Audit&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;audit_page - full on-page AI-SEO audit: headings, FAQ blocks, entity density, structured data&lt;/li&gt;
&lt;li&gt;audit_schema - Schema.org validation and deprecation check&lt;/li&gt;
&lt;li&gt;audit_canonical - canonical tag audit and redirect chain inspection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Technical&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check_robots - robots.txt parse and AI-crawler directive check (GPTBot, ClaudeBot, PerplexityBot)&lt;/li&gt;
&lt;li&gt;check_sitemap - sitemap structure, freshness, and coverage check&lt;/li&gt;
&lt;li&gt;check_technical - Core Web Vitals summary and crawlability flags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scoring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;score_ai_overview_eligibility - predicts likelihood of inclusion in Google AI Overviews&lt;/li&gt;
&lt;li&gt;score_citation_worthiness - composite score for LLM citation probability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Content&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate_llms_txt - generates a draft llms.txt for a site&lt;/li&gt;
&lt;li&gt;validate_llms_txt - validates an existing llms.txt against the spec&lt;/li&gt;
&lt;li&gt;extract_entities - pulls named entities and semantic clusters from page content&lt;/li&gt;
&lt;li&gt;rewrite_for_aeo - rewrites a passage for Answer Engine Optimization&lt;/li&gt;
&lt;li&gt;rewrite_for_geo - rewrites content for Generative Engine Optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Add to your claude_desktop_config.json (or equivalent for Cursor / Cline):&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;"ai-seo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@automatelab/ai-seo-mcp"&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;Or run it once to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @automatelab/ai-seo-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;After adding the server and asking Claude to audit a page, it runs audit_page and returns something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI-SEO audit: automatelab.tech/n8n-mcp-server/&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Citation worthiness: 72/100&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FAQ block present: yes (4 questions)&lt;/li&gt;
&lt;li&gt;Structured data: HowTo schema detected, no deprecation issues&lt;/li&gt;
&lt;li&gt;Entity density: moderate - consider adding more named tools/versions&lt;/li&gt;
&lt;li&gt;H2 headings: 6 match common query phrasing&lt;/li&gt;
&lt;li&gt;AI-crawler access: GPTBot allowed, PerplexityBot allowed, ClaudeBot allowed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Top recommendation:&lt;/strong&gt; Add a direct-answer paragraph in the first 150 words. AI Overviews pull from the top of the page first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That output comes back inside your Claude conversation - no tab-switching, no dashboard login. You can then ask Claude to rewrite_for_aeo the intro block and re-audit in the same session.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;The next version will add per-tool caching so repeated audits on the same URL skip re-fetching, and a batch_audit tool for running a full site check against a sitemap. Feedback welcome - open an issue on the repo or reply here.&lt;/p&gt;




&lt;p&gt;Repo: &lt;a href="https://github.com/AutomateLab-tech/ai-seo" rel="noopener noreferrer"&gt;https://github.com/AutomateLab-tech/ai-seo&lt;/a&gt;&lt;br&gt;
Landing: &lt;a href="https://automatelab.tech/products/mcp/ai-seo/" rel="noopener noreferrer"&gt;https://automatelab.tech/products/mcp/ai-seo/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>claude</category>
      <category>aiseo</category>
    </item>
    <item>
      <title>Claude Code MCP Server Setup on Windows</title>
      <dc:creator>Artyom Rabzonov</dc:creator>
      <pubDate>Thu, 14 May 2026 10:59:46 +0000</pubDate>
      <link>https://dev.to/ratamaha/claude-code-mcp-server-setup-on-windows-4clo</link>
      <guid>https://dev.to/ratamaha/claude-code-mcp-server-setup-on-windows-4clo</guid>
      <description>&lt;h2&gt;
  
  
  How to Set Up an MCP Server in Claude Code on Windows
&lt;/h2&gt;

&lt;p&gt;Add HTTP, SSE, and stdio MCP servers to Claude Code on Windows. The &lt;code&gt;cmd /c npx&lt;/code&gt; wrapper, the three scopes, and the errors you hit if you skip the wrapper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Use &lt;code&gt;claude mcp add&lt;/code&gt; for remote HTTP servers without modification. Wrap any stdio server running through &lt;code&gt;npx&lt;/code&gt; with &lt;code&gt;cmd /c&lt;/code&gt;. Without the wrapper, npx fails to spawn and the server appears in &lt;code&gt;claude mcp list&lt;/code&gt; but never connects.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code installed.&lt;/strong&gt; Run &lt;code&gt;claude --version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 18 or newer&lt;/strong&gt; on PATH if you plan to run stdio servers via npx.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A working terminal.&lt;/strong&gt; Windows PowerShell, Windows Terminal, or Git Bash. WSL is not required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pick a Scope Before You Add a Server
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Loads in&lt;/th&gt;
&lt;th&gt;Shared with team&lt;/th&gt;
&lt;th&gt;Stored in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;local (default)&lt;/td&gt;
&lt;td&gt;Current project only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;~/.claude.json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;project&lt;/td&gt;
&lt;td&gt;Current project only&lt;/td&gt;
&lt;td&gt;Yes, via Git&lt;/td&gt;
&lt;td&gt;.mcp.json in project root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;user&lt;/td&gt;
&lt;td&gt;All your projects&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;~/.claude.json&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Option 1: Add a Remote HTTP Server (No Windows-Specific Quirks)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http sentry https://mcp.sentry.dev/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For servers that authenticate with a token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http github https://api.githubcopilot.com/mcp/ &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_GITHUB_PAT"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Option 2: Add a Local Stdio Server (Where Windows Differs)
&lt;/h2&gt;

&lt;p&gt;On Windows, &lt;code&gt;npx&lt;/code&gt; resolves to &lt;code&gt;npx.cmd&lt;/code&gt;, a batch script the Node child-process spawn inside Claude Code does not invoke directly. The fix is to wrap the call with &lt;code&gt;cmd /c&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method A: Through the CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio filesystem &lt;span class="nt"&gt;--&lt;/span&gt; cmd /c npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelcontextprotocol/server-filesystem C:/Users/you/projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All flags come &lt;strong&gt;before&lt;/strong&gt; the server name. The double-dash &lt;code&gt;--&lt;/code&gt; separates the name from the command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method B: Edit .mcp.json Directly
&lt;/h3&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;"filesystem"&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;"cmd"&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;"/c"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-filesystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:/Users/you/projects"&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;Use forward slashes in path arguments. Restart Claude Code after editing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the Server Is Connected
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;claude mcp list&lt;/code&gt; - the server should appear.&lt;/li&gt;
&lt;li&gt;Inside Claude Code, type &lt;code&gt;/mcp&lt;/code&gt; - servers show as connected, pending, or failed.&lt;/li&gt;
&lt;li&gt;Use a tool to confirm the connection works end-to-end.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Common Errors
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;spawn npx ENOENT&lt;/strong&gt; - add the &lt;code&gt;cmd /c&lt;/code&gt; wrapper, or confirm Node is on PATH.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server failed to start&lt;/strong&gt; after OAuth - set &lt;code&gt;MCP_TIMEOUT=10000&lt;/code&gt; before launching Claude Code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This project MCP servers must be approved&lt;/strong&gt; - approve at the prompt, or run &lt;code&gt;claude mcp reset-project-choices&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server shows in list but never appears under /mcp&lt;/strong&gt; - flag ordering is wrong; re-run with all options before the name.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why does npx fail on Windows?&lt;/strong&gt; On Windows it resolves to &lt;code&gt;npx.cmd&lt;/code&gt;, a batch script the Node child-process spawn does not invoke as an executable. Wrapping with &lt;code&gt;cmd /c&lt;/code&gt; hands the script to the shell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is the MCP config file stored on Windows?&lt;/strong&gt; Local and user-scoped servers live in &lt;code&gt;%USERPROFILE%\.claude.json&lt;/code&gt;. Project-scoped servers live in &lt;code&gt;.mcp.json&lt;/code&gt; at the project root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need WSL?&lt;/strong&gt; No. The native Windows install of Claude Code runs MCP servers directly.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://automatelab.tech/claude-code-mcp-windows-setup/" rel="noopener noreferrer"&gt;automatelab.tech&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>windows</category>
      <category>ai</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
