<?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: Yonyon</title>
    <description>The latest articles on DEV Community by Yonyon (@yonyonai).</description>
    <link>https://dev.to/yonyonai</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3819756%2F48d4dde4-41e4-44a5-b714-9ced7c8ce432.jpg</url>
      <title>DEV Community: Yonyon</title>
      <link>https://dev.to/yonyonai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yonyonai"/>
    <language>en</language>
    <item>
      <title>OrchestKit scored 83/100 on ORA — what "agent-readiness" actually measures</title>
      <dc:creator>Yonyon</dc:creator>
      <pubDate>Wed, 17 Jun 2026 06:43:34 +0000</pubDate>
      <link>https://dev.to/yonyonai/orchestkit-scored-83100-on-ora-what-agent-readiness-actually-measures-3pkj</link>
      <guid>https://dev.to/yonyonai/orchestkit-scored-83100-on-ora-what-agent-readiness-actually-measures-3pkj</guid>
      <description>&lt;p&gt;I ran &lt;a href="https://orchestkit.yonyon.ai" rel="noopener noreferrer"&gt;OrchestKit&lt;/a&gt; — my open-source Claude Code toolkit — through &lt;strong&gt;ORA&lt;/strong&gt;, an independent audit that scores how &lt;em&gt;agent-ready&lt;/em&gt; a product is: how easily an AI agent can discover you, identify itself, authenticate, and actually get work done against your surface.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;83/100, Grade B ("Competitive"), ranked #49 of 12,200.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The breakdown
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auth &amp;amp; Access&lt;/td&gt;
&lt;td&gt;28/30&lt;/td&gt;
&lt;td&gt;🟢 Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent Integration&lt;/td&gt;
&lt;td&gt;18/20&lt;/td&gt;
&lt;td&gt;🟢 Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity&lt;/td&gt;
&lt;td&gt;18/20&lt;/td&gt;
&lt;td&gt;🟢 Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Experience&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;🟢 Perfect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovery&lt;/td&gt;
&lt;td&gt;9/20&lt;/td&gt;
&lt;td&gt;🟠 Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I take from it
&lt;/h2&gt;

&lt;p&gt;The foundation is strong: an agent can identify, connect, and use OrchestKit with near-zero friction — exactly what an agent-native toolkit should be.&lt;/p&gt;

&lt;p&gt;The one hole is &lt;strong&gt;Discovery&lt;/strong&gt; — presence, not product. The thing is ready; it just needs more people (and agents) to know it exists. Honestly the most fun problem to have, and it's the next focus.&lt;/p&gt;

&lt;p&gt;Solo-built. Open-source. Independently verified.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://orchestkit.yonyon.ai" rel="noopener noreferrer"&gt;https://orchestkit.yonyon.ai&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>mcp</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Making your docs site agent-readable: llms.txt, MCP, and the .well-known files that actually matter</title>
      <dc:creator>Yonyon</dc:creator>
      <pubDate>Sun, 14 Jun 2026 17:13:20 +0000</pubDate>
      <link>https://dev.to/yonyonai/making-your-docs-site-agent-readable-llmstxt-mcp-and-the-well-known-files-that-actually-matter-33c6</link>
      <guid>https://dev.to/yonyonai/making-your-docs-site-agent-readable-llmstxt-mcp-and-the-well-known-files-that-actually-matter-33c6</guid>
      <description>&lt;p&gt;AI agents increasingly read your docs &lt;em&gt;instead of&lt;/em&gt; a human. If your documentation site only emits HTML for a browser, an agent has to scrape and guess. There's a better surface — and most of it is a handful of small, standard files. Here's the full stack we ship on the OrchestKit docs site, why each piece exists, and how to verify it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;llms.txt&lt;/code&gt; — the agent's table of contents
&lt;/h2&gt;

&lt;p&gt;A plain-text index at &lt;code&gt;/llms.txt&lt;/code&gt;: what the product is, its constraints, and a link map to every machine-readable resource. Keep it under ~30k chars; put the exhaustive page list in &lt;code&gt;/docs/llms.txt&lt;/code&gt; and the full corpus in &lt;code&gt;/llms-full.txt&lt;/code&gt;. The win: an agent gets oriented in one fetch instead of crawling.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Markdown content negotiation
&lt;/h2&gt;

&lt;p&gt;Append &lt;code&gt;.md&lt;/code&gt; to any page URL (or send &lt;code&gt;Accept: text/markdown&lt;/code&gt;) and return the raw Markdown. Agents get clean tokens; humans still get the rendered page.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. An OpenAPI spec for your read APIs
&lt;/h2&gt;

&lt;p&gt;Even a docs site has an API surface (search, page fetch). Publish an OpenAPI document at a predictable path so an agent can call it without reverse-engineering. Pair it with RFC 9727 — a &lt;code&gt;/.well-known/api-catalog&lt;/code&gt; linkset that enumerates every API entry point.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. An MCP server
&lt;/h2&gt;

&lt;p&gt;The Model Context Protocol lets agents call your tools natively. We expose a read-only MCP server over Streamable HTTP at &lt;code&gt;/api/mcp&lt;/code&gt; plus a discovery &lt;code&gt;server-card.json&lt;/code&gt;. Two tools — search docs, get a doc by id — are enough to be useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The &lt;code&gt;.well-known&lt;/code&gt; identity files
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agent-card.json&lt;/code&gt; (A2A): declares your agent skills.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent-skills/index.json&lt;/code&gt;: the Agent Skills Discovery RFC, with a SHA-256 digest per skill so a consumer can verify it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;oauth-protected-resource&lt;/code&gt; (RFC 9728): if your API is anonymous, &lt;em&gt;say so&lt;/em&gt; — an empty &lt;code&gt;authorization_servers&lt;/code&gt; is a positive signal, not an omission.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. JSON-LD that an entity graph can reconcile
&lt;/h2&gt;

&lt;p&gt;Emit a &lt;code&gt;schema.org&lt;/code&gt; graph (&lt;code&gt;Organization&lt;/code&gt;, &lt;code&gt;SoftwareApplication&lt;/code&gt;, &lt;code&gt;WebSite&lt;/code&gt;) linked by &lt;code&gt;@id&lt;/code&gt;, with &lt;code&gt;sameAs&lt;/code&gt; pointing at the registries that already verify you (GitHub, your package registry, Wikidata). One canonical Organization block, reused everywhere, so the graph never sees conflicting identifiers. Never fabricate an &lt;code&gt;aggregateRating&lt;/code&gt; — surface real signals (e.g. GitHub stars as an &lt;code&gt;InteractionCounter&lt;/code&gt;) instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Tell crawlers the truth in &lt;code&gt;robots.txt&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Explicitly allow the named AI crawlers you want (GPTBot, ClaudeBot, OAI-SearchBot, Google-Extended…), and emit a &lt;code&gt;Content-Signal&lt;/code&gt; directive. Link your sitemap and a schema-map.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;curl -s https://yoursite/llms.txt&lt;/code&gt;, fetch each &lt;code&gt;.well-known&lt;/code&gt; path, and run your JSON-LD through a structured-data validator. If you build on Claude Code, the open-source &lt;strong&gt;OrchestKit&lt;/strong&gt; docs site implements every item above — the source is on GitHub, MIT-licensed, and you can read the route handlers directly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I maintain OrchestKit (a free, MIT plugin for Claude Code, 111 skills/37 agents/210 hooks). The agent-discovery surface described here is what its docs site ships today.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I made my portfolio readable by AI agents. A scanner found four things that didn't exist</title>
      <dc:creator>Yonyon</dc:creator>
      <pubDate>Thu, 11 Jun 2026 16:42:20 +0000</pubDate>
      <link>https://dev.to/yonyonai/i-made-my-portfolio-readable-by-ai-agents-a-scanner-found-four-things-that-didnt-exist-456o</link>
      <guid>https://dev.to/yonyonai/i-made-my-portfolio-readable-by-ai-agents-a-scanner-found-four-things-that-didnt-exist-456o</guid>
      <description>&lt;p&gt;Last week I wrote about &lt;a href="https://dev.to/yonyonai/how-i-made-my-website-fully-agent-readable-an-mcp-server-nlweb-ask-in-nextjs-50m0"&gt;making my site fully agent-readable&lt;/a&gt; — an MCP server, an NLWeb &lt;code&gt;/ask&lt;/code&gt; endpoint, &lt;code&gt;llms.txt&lt;/code&gt;, a &lt;code&gt;.well-known/&lt;/code&gt; discovery tree. Shipping those was the easy half.&lt;/p&gt;

&lt;p&gt;Then I started scoring the site with an agent-readiness scanner — a prober that visits like a real agent: no JavaScript execution, spec-shaped requests, no goodwill. Every round it found something that &lt;em&gt;worked&lt;/em&gt; in my browser and &lt;strong&gt;did not exist&lt;/strong&gt; as far as an agent could tell.&lt;/p&gt;

&lt;p&gt;These are the four that stung. All real code from &lt;a href="https://yonyon.ai" rel="noopener noreferrer"&gt;yonyon.ai&lt;/a&gt;, all fixed this round.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The "helpful" GET that made my endpoint read as missing
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;/ask&lt;/code&gt; endpoint (Microsoft's &lt;a href="https://github.com/nlweb-ai/NLWeb" rel="noopener noreferrer"&gt;NLWeb&lt;/a&gt; shape) answered &lt;code&gt;POST { "query": "..." }&lt;/code&gt;. For &lt;code&gt;GET&lt;/code&gt;, I did what felt like good API manners — return a machine-readable descriptor telling the caller how to use the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GET /ask — the old version&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;response_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;nlws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NLWEB_VERSION&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="s1"&gt;POST { "query": "..." } for answers...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Polite, self-documenting, useless. The NLWeb &lt;strong&gt;reference client&lt;/strong&gt; — and therefore the scanner — issues &lt;code&gt;GET /ask?query=...&lt;/code&gt; and expects an answer. It got my descriptor back, which is not an answer, so the endpoint graded as &lt;em&gt;not implemented&lt;/em&gt;. My documentation-instead-of-data response was indistinguishable from a stub.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;GET&lt;/code&gt; with a query parameter runs the real pipeline (including SSE via &lt;code&gt;?streaming=true&lt;/code&gt;); a bare &lt;code&gt;GET&lt;/code&gt; keeps the descriptor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query&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;q&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;question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handleAsk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// same pipeline as POST, SSE supported&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// bare GET still teaches probing agents how to call it&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; agents don't read your descriptor and adapt — they call you the way &lt;em&gt;their&lt;/em&gt; reference client calls everyone. Implement the spec's client behavior, not your idea of good manners.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The WebMCP tools nobody could see
&lt;/h2&gt;

&lt;p&gt;I exposed in-browser tools via &lt;a href="https://webmachinelearning.github.io/webmcp/" rel="noopener noreferrer"&gt;WebMCP&lt;/a&gt; (&lt;code&gt;navigator.modelContext&lt;/code&gt;, Chrome early preview): &lt;code&gt;ask_yonatan&lt;/code&gt;, &lt;code&gt;browse_projects&lt;/code&gt;, &lt;code&gt;book_intro_call&lt;/code&gt;. The natural React implementation is a client component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;WebMcpTools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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;provide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&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="nx"&gt;provideContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;provide&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;Feature-detected, try/caught, tidy. Also &lt;strong&gt;invisible&lt;/strong&gt;. Crawlers and scanners detect WebMCP support by scanning the &lt;em&gt;unhydrated&lt;/em&gt; document — they don't run your bundle, and even executing browsers without the experimental API skip the registration entirely. A hydration-gated capability has no static trace. As far as any external observer was concerned, the site had no WebMCP support at all.&lt;/p&gt;

&lt;p&gt;The fix is almost embarrassing: a server component that emits the registration as an &lt;strong&gt;inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;&lt;/strong&gt;, so it's right there in the raw HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server component — no hydration involved&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WEBMCP_REGISTRATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="s2"&gt;`(function () {
  var mc = navigator.modelContext;
  if (!mc || typeof mc.provideContext !== "function") return;
  mc.provideContext({ tools: [ /* same tools, plain JS */ ] });
})();`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;WebMcpScript&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WEBMCP_REGISTRATION&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Next.js already requires &lt;code&gt;'unsafe-inline'&lt;/code&gt; in CSP, so this adds no new policy cost. If your CSP is stricter, use a nonce.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; a runtime-registered agent capability needs a statically visible twin. If it can't be seen in &lt;code&gt;curl&lt;/code&gt; output, it doesn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The status page that 404'd in production only
&lt;/h2&gt;

&lt;p&gt;I added &lt;code&gt;/status&lt;/code&gt; — a tiny health page with content negotiation: JSON for &lt;code&gt;Accept: application/json&lt;/code&gt;, HTML for humans. It worked in dev. In production: &lt;strong&gt;404&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The culprit was &lt;code&gt;next-intl&lt;/code&gt;'s middleware. My site is internationalized, and the middleware matches every page-ish path and routes it into the &lt;code&gt;[locale]&lt;/code&gt; tree. I'd even left myself a comment claiming route handlers escape the middleware. They don't. Bare &lt;code&gt;/status&lt;/code&gt; was being locale-routed into &lt;code&gt;/[locale]/status&lt;/code&gt;, which doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/proxy.ts — the matcher had to exclude /status explicitly&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/((?!api|studio|trpc|_next|_vercel|status|.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;..*).*)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there was a second bug hiding behind the first: I'd marked the route &lt;code&gt;force-static&lt;/code&gt; for cheapness. Static means Next.js prerenders &lt;strong&gt;one&lt;/strong&gt; variant — with an empty &lt;code&gt;Accept&lt;/code&gt; header — and serves that frozen response to everyone. My own content negotiation could never run. It had to be &lt;code&gt;force-dynamic&lt;/code&gt; with &lt;code&gt;Vary: Accept&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; i18n middleware and agent endpoints fight over the same URL space, and the agent endpoints lose silently. Every bare path you promise agents (&lt;code&gt;/ask&lt;/code&gt;, &lt;code&gt;/mcp&lt;/code&gt;, &lt;code&gt;/status&lt;/code&gt;) needs an explicit carve-out — and content negotiation is incompatible with static prerendering by definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;ai-train=yes&lt;/code&gt;, on purpose
&lt;/h2&gt;

&lt;p&gt;Most advice about AI crawlers is defensive: block GPTBot, block CCBot, opt out of training. My robots.txt does the opposite, explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Content Signals (https://contentsignals.org). Discovery is a goal for this
# site, so AI training is allowed — being in training corpora is the point.
&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;-&lt;span class="n"&gt;Signal&lt;/span&gt;: &lt;span class="n"&gt;search&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;, &lt;span class="n"&gt;ai&lt;/span&gt;-&lt;span class="n"&gt;input&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;, &lt;span class="n"&gt;ai&lt;/span&gt;-&lt;span class="n"&gt;train&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a portfolio. Its entire job is to be &lt;em&gt;found&lt;/em&gt; when someone — increasingly, someone's agent — asks "who can build a production RAG system?" An assistant that learned about my work during training, or retrieves it at inference time, is doing my marketing. Blocking that to protect content whose value is being known would be self-defeating.&lt;/p&gt;

&lt;p&gt;That trade-off is deliberate and it isn't right for everyone — if your content &lt;em&gt;is&lt;/em&gt; the product, sell it, don't donate it. But make the choice consciously. The default-deny advice assumes your content's value is captured by humans reading it on your domain. For a personal site optimizing for discovery, the math runs the other way. (The same file blocks Bytespider and other bulk scrapers that offer no assistant or search surface — welcome the tier that cites you, refuse the tier that doesn't.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern under all four
&lt;/h2&gt;

&lt;p&gt;"Agent-readable" turned out to be two separate properties, and I kept confusing them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Works when called correctly&lt;/strong&gt; — the endpoint answers, the tool executes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverable by something that won't call you correctly&lt;/strong&gt; — no JS execution, spec-default request shapes, bare paths, raw HTML.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything above failed on the second property while passing the first. My browser hydrated the WebMCP component; the scanner read static HTML. My docs explained &lt;code&gt;POST /ask&lt;/code&gt;; the prober sent &lt;code&gt;GET&lt;/code&gt;. Dev served &lt;code&gt;/status&lt;/code&gt;; production's middleware ate it.&lt;/p&gt;

&lt;p&gt;The only thing that caught any of this was pointing an external, JS-free, spec-literal scanner at the production domain and treating its score as the truth. Your own browser is the worst possible test client for agent-readability — it's too forgiving.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full implementation (MCP server, NLWeb endpoint, llms.txt, .well-known tree) is covered in &lt;a href="https://dev.to/yonyonai/how-i-made-my-website-fully-agent-readable-an-mcp-server-nlweb-ask-in-nextjs-50m0"&gt;part one&lt;/a&gt;. Try the live surfaces: &lt;code&gt;curl -X POST https://yonyon.ai/ask -d '{"query":"what does yonatan build?"}'&lt;/code&gt; or &lt;code&gt;GET https://yonyon.ai/status&lt;/code&gt; with &lt;code&gt;Accept: application/json&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>agents</category>
    </item>
    <item>
      <title>How I made my website fully agent-readable: an MCP server + NLWeb /ask in Next.js</title>
      <dc:creator>Yonyon</dc:creator>
      <pubDate>Tue, 09 Jun 2026 10:13:55 +0000</pubDate>
      <link>https://dev.to/yonyonai/how-i-made-my-website-fully-agent-readable-an-mcp-server-nlweb-ask-in-nextjs-50m0</link>
      <guid>https://dev.to/yonyonai/how-i-made-my-website-fully-agent-readable-an-mcp-server-nlweb-ask-in-nextjs-50m0</guid>
      <description>&lt;h1&gt;
  
  
  How I made my website fully agent-readable: an MCP server + NLWeb /ask in Next.js
&lt;/h1&gt;

&lt;p&gt;Most websites are built for humans with browsers. Increasingly, the visitor is an &lt;strong&gt;AI agent&lt;/strong&gt; — Claude, a custom assistant, some autonomous tool — and it doesn't want your hero animation. It wants structured, machine-readable answers and &lt;em&gt;actions it can take&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I rebuilt my own site, &lt;a href="https://yonyon.ai" rel="noopener noreferrer"&gt;yonyon.ai&lt;/a&gt;, to be readable and &lt;em&gt;usable&lt;/em&gt; by agents end to end: a live &lt;code&gt;/ask&lt;/code&gt; endpoint (Microsoft's NLWeb shape), a real MCP server at &lt;code&gt;/mcp&lt;/code&gt;, in-browser WebMCP tools, plus &lt;code&gt;llms.txt&lt;/code&gt; and a &lt;code&gt;.well-known/&lt;/code&gt; discovery tree. This post is the worked example — the three pieces that did the heavy lifting, with the actual code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: locale routing eats your agent endpoints
&lt;/h2&gt;

&lt;p&gt;My site is internationalized with &lt;code&gt;next-intl&lt;/code&gt;. Its middleware matches every path and routes it into a &lt;code&gt;[locale]&lt;/code&gt; segment. So the moment I added a bare &lt;code&gt;/ask&lt;/code&gt; route for agents, the i18n middleware grabbed it first and tried to render &lt;code&gt;/[locale]/ask&lt;/code&gt; — &lt;strong&gt;404&lt;/strong&gt;. Same problem would hit &lt;code&gt;/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Agents expect &lt;em&gt;bare, unprefixed&lt;/em&gt; paths (&lt;code&gt;/ask&lt;/code&gt;, &lt;code&gt;/mcp&lt;/code&gt;) — not &lt;code&gt;/en/ask&lt;/code&gt;. The fix is to intercept those paths &lt;strong&gt;in the proxy/middleware, before the i18n matcher runs&lt;/strong&gt;, and rewrite them onto the real API route. &lt;code&gt;rewrite()&lt;/code&gt; (not &lt;code&gt;redirect()&lt;/code&gt;) preserves the HTTP method and body, so a &lt;code&gt;POST /ask&lt;/code&gt; stays a &lt;code&gt;POST&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/proxy.ts  (next-intl middleware entrypoint)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;createMiddleware&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-intl/middleware&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./i18n/routing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intlMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Rewrite the bare /ask onto the /api/ask route handler BEFORE next-intl&lt;/span&gt;
  &lt;span class="c1"&gt;// pulls it into the [locale] tree (which would 404). rewrite() keeps the&lt;/span&gt;
  &lt;span class="c1"&gt;// method + body, so POST /ask works.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/ask&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/ask&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&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="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;intlMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Skip /api, /_next, static files, etc. — only run on page-ish paths.&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/((?!api|studio|trpc|_next|_vercel|.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;..*).*)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole alias trick: &lt;strong&gt;bare agent paths are matched first and rewritten into &lt;code&gt;/api/*&lt;/code&gt;&lt;/strong&gt;, so they never reach the locale router. Add a line per endpoint you want to expose unprefixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A dependency-free MCP server
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is how agents discover and call your tools. The official SDK is great — but it peer-depends on &lt;strong&gt;zod 3&lt;/strong&gt;, and my repo is on &lt;strong&gt;zod 4&lt;/strong&gt;. Rather than fight the dependency tree, I hand-rolled the handler. MCP's Streamable-HTTP transport is just &lt;strong&gt;JSON-RPC 2.0 over POST&lt;/strong&gt;, and I only needed a handful of methods: &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;tools/call&lt;/code&gt;, &lt;code&gt;ping&lt;/code&gt;. Zero dependency risk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/mcp/route.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;force-dynamic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SERVER_INFO&lt;/span&gt; &lt;span class="o"&gt;=&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;yonyon.ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROTOCOL_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2025-06-18&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOOLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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;ask_yonatan&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;Ask about Yonatan Gross's work, projects, or experience.&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;question&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;maxLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&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;question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// Behavioural hints so agents can reason about side effects:&lt;/span&gt;
    &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;destructiveHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;openWorldHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// browse_projects, book_intro_call ...&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;rpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rpcError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;initialize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;protocolVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PROTOCOL_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tools&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="na"&gt;serverInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SERVER_INFO&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tools/list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TOOLS&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tools/call&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rpcError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32602&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Unknown tool: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// structured error, not soft text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rpcError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32601&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Method not found: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that matter for agent ergonomics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool annotations&lt;/strong&gt; (&lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt;, &lt;code&gt;openWorldHint&lt;/code&gt;) let a planner decide whether a tool is safe to call autonomously.&lt;/li&gt;
&lt;li&gt;An unknown tool returns a &lt;strong&gt;structured JSON-RPC error&lt;/strong&gt; (&lt;code&gt;code&lt;/code&gt; + &lt;code&gt;message&lt;/code&gt;), not a friendly text blob — so agents branch on it programmatically instead of string-matching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Discovery: llms.txt + .well-known
&lt;/h2&gt;

&lt;p&gt;Endpoints are useless if nothing can find them. Two conventions cover it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/llms.txt&lt;/code&gt; — a human- and agent-readable map of the site, modeled on &lt;code&gt;robots.txt&lt;/code&gt; but for LLMs: who you are, what an agent can do here, and the exact endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# yonyon.ai — Yonatan Gross

&amp;gt; AI Platform Engineer &amp;amp; Backend Developer. Builds production AI systems
&amp;gt; end-to-end: RAG pipelines, multi-agent architectures, MCP servers.

## What an agent can do here
- Ask about my work — POST /ask with {"query":"..."}  (NLWeb, no auth)
- Browse projects, book a free 15-min intro call
- MCP server at /mcp (initialize, tools/list, tools/call)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.well-known/&lt;/code&gt; tree carries the machine-readable specs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public/.well-known/
├── mcp.json                 # points agents at /mcp
├── mcp/server-card.json     # MCP server identity card
├── openapi.json             # OpenAPI for the HTTP endpoints
├── api-catalog              # RFC 9727 linkset of all APIs
└── agent-skills/            # discrete "skills" (ask, browse, book) as SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because these live in &lt;code&gt;public/&lt;/code&gt;, they're served as plain static files — bypassing the locale router entirely, so there's no dotpath/404 issue. I also emit RFC 8288 &lt;code&gt;Link&lt;/code&gt; headers (&lt;code&gt;rel="describedby"&lt;/code&gt; → llms.txt, &lt;code&gt;rel="service-desc"&lt;/code&gt; → openapi.json) so an agent that only fetches headers still discovers everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payoff
&lt;/h2&gt;

&lt;p&gt;You can verify it live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yonyon.ai/ask &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query":"What does Yonatan build?"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single-shot, no-auth, agent-facing endpoint returns a grounded answer from the same RAG-backed model that powers the site chat. An agent can then call the MCP &lt;code&gt;book_intro_call&lt;/code&gt; tool and act.&lt;/p&gt;

&lt;p&gt;Building agent-ready surfaces is also just good practice for the corpus AI models train on — the more structured and citable your presence, the more likely a model recalls your work. I package these patterns (and a lot more) as &lt;strong&gt;&lt;a href="https://github.com/yonatangross/orchestkit" rel="noopener noreferrer"&gt;OrchestKit&lt;/a&gt;&lt;/strong&gt;, my open-source Claude Code agent framework — 111 skills, 37 agents, 211 hooks.&lt;/p&gt;

&lt;p&gt;If you're making something agent-readable and want a second pair of eyes, the front door at &lt;strong&gt;&lt;a href="https://yonyon.ai" rel="noopener noreferrer"&gt;yonyon.ai&lt;/a&gt;&lt;/strong&gt; is itself the demo — ask it a question, or grab a free 15-minute intro call.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
    <item>
      <title>Building an Automated Content Pipeline That Posts to 6 Platforms</title>
      <dc:creator>Yonyon</dc:creator>
      <pubDate>Thu, 12 Mar 2026 10:32:21 +0000</pubDate>
      <link>https://dev.to/yonyonai/building-an-automated-content-pipeline-that-posts-to-6-platforms-h8a</link>
      <guid>https://dev.to/yonyonai/building-an-automated-content-pipeline-that-posts-to-6-platforms-h8a</guid>
      <description>&lt;p&gt;Every developer knows the pain: you write a great article, publish it on one platform, and then spend the next hour manually reformatting and posting it everywhere else.&lt;/p&gt;

&lt;p&gt;I built an automated content distribution pipeline that publishes to 6 platforms from a single source of truth. Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Write once, distribute everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hashnode&lt;/strong&gt; (source of truth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev.to&lt;/strong&gt; (you're reading this here)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn&lt;/strong&gt; (article share + commentary)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threads&lt;/strong&gt; (text teaser)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instagram&lt;/strong&gt; (card image + caption)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;daily.dev&lt;/strong&gt; (auto via RSS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each platform gets a tailored variant — not a blind copy-paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Platform Adapter Pattern
&lt;/h2&gt;

&lt;p&gt;Each platform implements a common interface with a single &lt;code&gt;publish()&lt;/code&gt; method. Adding a new platform is just implementing one class. Rate limiting is Redis-backed per platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart Content Composition
&lt;/h2&gt;

&lt;p&gt;The hardest part isn't the API calls — it's making content feel native. Twitter gets a 280-char hook. Threads gets a 500-char teaser. LinkedIn gets a 3,000-char article share.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quality Ladder
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Ollama (local, free) generates the first draft&lt;/li&gt;
&lt;li&gt;Quality gate checks readability and boring-start patterns&lt;/li&gt;
&lt;li&gt;Claude Haiku polishes if needed (~$0.002)&lt;/li&gt;
&lt;li&gt;Template fallback as safety net&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Feedback Loop
&lt;/h2&gt;

&lt;p&gt;A weekly task evaluates performance, extracts patterns, and injects learnings into future prompts. The system gets smarter over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.13, FastAPI, SQLAlchemy 2 async, Celery&lt;/li&gt;
&lt;li&gt;Next.js 16, TypeScript, TanStack Query&lt;/li&gt;
&lt;li&gt;AI quality ladder (Ollama → Haiku), RAG, pgvector&lt;/li&gt;
&lt;li&gt;1Password secrets, Redis rate limiting&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This post was cross-posted using the pipeline described above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>api</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
