<?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: FavCRM</title>
    <description>The latest articles on DEV Community by FavCRM (@favcrm).</description>
    <link>https://dev.to/favcrm</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%2F3769893%2F1f9df40a-7dd4-41aa-8c58-40f53348a268.png</url>
      <title>DEV Community: FavCRM</title>
      <link>https://dev.to/favcrm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/favcrm"/>
    <language>en</language>
    <item>
      <title>I built a client's booking site in an afternoon (AI for the UI, headless CRM for the hard parts)</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Wed, 03 Jun 2026 15:41:58 +0000</pubDate>
      <link>https://dev.to/favcrm/i-built-a-clients-booking-site-in-an-afternoon-ai-for-the-ui-headless-crm-for-the-hard-parts-20hc</link>
      <guid>https://dev.to/favcrm/i-built-a-clients-booking-site-in-an-afternoon-ai-for-the-ui-headless-crm-for-the-hard-parts-20hc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Syndicated from the &lt;a href="https://favcrm.io/blog/build-client-booking-app" rel="noopener noreferrer"&gt;FavCRM blog&lt;/a&gt;. The old quote was two weeks. With an agent on the UI and a headless backend, it's an afternoon.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A client needs a booking site. The old quote was two weeks: a calendar, a database, an availability engine, payments, a customer table.&lt;/p&gt;

&lt;p&gt;With an AI agent building the frontend and FavCRM as the &lt;a href="https://favcrm.io/campaigns/headless-crm" rel="noopener noreferrer"&gt;headless backend&lt;/a&gt;, the real work is an afternoon. Here is the whole job, start to finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;A small clinic. Three services, one practitioner, online booking with deposit. You are the agency; you have an AI agent in your editor and a terminal.&lt;/p&gt;

&lt;p&gt;The plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register the clinic's FavCRM workspace&lt;/li&gt;
&lt;li&gt;Configure services and availability&lt;/li&gt;
&lt;li&gt;Wire one server route that talks to FavCRM&lt;/li&gt;
&lt;li&gt;Let the agent build the booking UI against that route&lt;/li&gt;
&lt;li&gt;Test a real booking end to end&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1 — Register the workspace (~5 min)
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;favcrm&lt;/code&gt; CLI registers a workspace and issues an API key. No dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm signup request &lt;span class="nt"&gt;--email&lt;/span&gt; clinic@example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--organisation-name&lt;/span&gt; &lt;span class="s2"&gt;"Bright Smile Clinic"&lt;/span&gt;
favcrm signup verify &lt;span class="nt"&gt;--request-id&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;--code&lt;/span&gt; &amp;lt;6-digit-code&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verify step prints a &lt;code&gt;fav_mcp_*&lt;/code&gt; key. Put it where your build can read it — never in the repo:&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;FAVCRM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fav_mcp_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 — Configure services and availability (~30 min)
&lt;/h2&gt;

&lt;p&gt;Hand the brief to your agent and let it call the tools. Inspect a schema first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool describe create_service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create each service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call create_service &lt;span class="s1"&gt;'{
  "name": "New Patient Exam",
  "durationMinutes": 45,
  "price": "80.00"
}'&lt;/span&gt;
favcrm tool call create_service &lt;span class="s1"&gt;'{
  "name": "Cleaning",
  "durationMinutes": 30,
  "price": "60.00"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set when the practitioner works, so availability is real:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call set_staff_availability &lt;span class="s1"&gt;'{
  "weekday": "mon",
  "start": "09:00",
  "end": "17:00"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat per weekday. At this point the backend is done — services, hours, an availability engine that knows about clashes. You wrote no schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — One server route (~30 min)
&lt;/h2&gt;

&lt;p&gt;The browser must never hold the API key. Put it in one server route that proxies two operations: read slots, create a booking. FavCRM's MCP endpoint speaks JSON-RPC over HTTP, so a route handler can call it directly.&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="c1"&gt;// app/api/booking/route.js  (Next.js route handler)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MCP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.favcrm.io/mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MCP&lt;/span&gt;&lt;span class="p"&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="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&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="s1"&gt;content-type&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="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAVCRM_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="s1"&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="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="s1"&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="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&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="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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`FavCRM &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&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="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;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="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;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&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;slots&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;callTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_available_slots&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;serviceId&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serviceId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;date&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&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;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="nx"&gt;slots&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;booking&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;callTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create_booking&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;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customer&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="nx"&gt;body&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;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;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="nx"&gt;booking&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 is the entire backend you write — a proxy. &lt;code&gt;create_booking&lt;/code&gt; clash-checks, persists the row, and upserts the customer. Your route does no business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — The booking UI (~1–2 hrs)
&lt;/h2&gt;

&lt;p&gt;This is where the agent earns its keep. Prompt it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build a booking page. Fetch services, let the visitor pick one and a date,
GET /api/booking for open slots, POST /api/booking to confirm.
Show a success state with the booking time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent produces the React. It is calling your route, your route is calling FavCRM. The UI is yours to style for the clinic's brand — the parts that are tedious and error-prone (availability, persistence, clashes) are not your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Test a real booking
&lt;/h2&gt;

&lt;p&gt;Run the flow in the browser, then confirm it landed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call list_bookings &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The booking is there. So is the patient — &lt;code&gt;create_booking&lt;/code&gt; upserted a &lt;code&gt;crm_accounts&lt;/code&gt; record with their details and history. When they book again, it attaches to the same record.&lt;/p&gt;

&lt;p&gt;To take the deposit, add one more proxied call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool describe create_invoice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What shipped, and what you skipped
&lt;/h2&gt;

&lt;p&gt;The clinic has a branded booking site, real bookings, a patient list that fills itself, and Stripe deposits.&lt;/p&gt;

&lt;p&gt;You skipped: the database, the migrations, the availability algorithm, the double-booking bug you would have shipped and fixed in week two, the customer table, the Stripe webhook reconciliation.&lt;/p&gt;

&lt;p&gt;You wrote: a service config, one proxy route, and a UI.&lt;/p&gt;

&lt;p&gt;That is what a &lt;a href="https://favcrm.io/campaigns/headless-crm" rel="noopener noreferrer"&gt;headless CRM&lt;/a&gt; is for. The free tier covers a build like this end to end — point your agent at it and run the afternoon yourself.&lt;/p&gt;

&lt;p&gt;New to the category? Start with &lt;a href="https://favcrm.io/blog/agentic-crm-explained" rel="noopener noreferrer"&gt;what an agentic CRM is&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Tool count is a vanity metric. Annotation coverage is what makes an AI agent safe.</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Wed, 03 Jun 2026 15:41:31 +0000</pubDate>
      <link>https://dev.to/favcrm/tool-count-is-a-vanity-metric-annotation-coverage-is-what-makes-an-ai-agent-safe-1a4p</link>
      <guid>https://dev.to/favcrm/tool-count-is-a-vanity-metric-annotation-coverage-is-what-makes-an-ai-agent-safe-1a4p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Syndicated from the &lt;a href="https://favcrm.io/blog/mcp-tool-annotations-safe-agents" rel="noopener noreferrer"&gt;FavCRM blog&lt;/a&gt;. The number that predicts whether an agent is safe to let loose isn't the tool count.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When people compare agentic CRMs, they count tools. The number that actually predicts whether an agent is safe to let loose is a different one: &lt;strong&gt;annotation coverage&lt;/strong&gt;. An MCP tool annotation tells the agent what a tool &lt;em&gt;does to the world&lt;/em&gt; — whether it reads or mutates, whether it's safe to retry, whether it reaches an external service. Without annotations, the agent is guessing. This is what they are, and why a catalog's annotation coverage matters more than its tool count.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MCP annotation is
&lt;/h2&gt;

&lt;p&gt;Every MCP tool can carry hints alongside its input and output schemas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;readOnlyHint&lt;/code&gt;&lt;/strong&gt; — the tool only reads; it changes nothing. Safe to call freely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;destructiveHint&lt;/code&gt;&lt;/strong&gt; — the tool mutates or deletes. The agent should confirm before calling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;idempotentHint&lt;/code&gt;&lt;/strong&gt; — calling it twice with the same input has the same effect as once. Safe to retry on a timeout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;openWorldHint&lt;/code&gt;&lt;/strong&gt; — the tool reaches an external service (sends an email, charges a card), so its effects leave the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not documentation for humans. They are machine-readable signals the agent reasons over before it acts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why they prevent the worst failures
&lt;/h2&gt;

&lt;p&gt;The dangerous class of agent failure is not "the agent couldn't do something." It's "the agent did the wrong destructive thing because it misread an ambiguous instruction." Delete the customer instead of the tag. Refund the wrong invoice. Cancel every booking instead of one.&lt;/p&gt;

&lt;p&gt;Annotations let the agent self-gate. A well-annotated catalog means the agent calls &lt;code&gt;list_members&lt;/code&gt; without ceremony but pauses to confirm before &lt;code&gt;cancel_booking&lt;/code&gt;, because one is marked read-only and the other destructive. Pre-MCP function-calling had no equivalent — every tool looked the same to the model, so safety lived entirely in the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why coverage matters more than count
&lt;/h2&gt;

&lt;p&gt;A 190+ tool catalog with 100% annotation coverage is safer than a 30-tool catalog with none. A tool that lacks a &lt;code&gt;destructiveHint&lt;/code&gt; is a landmine: the agent has no way to know it's dangerous until it has already called it. So when you evaluate an &lt;a href="https://favcrm.io/blog/agentic-crm-explained" rel="noopener noreferrer"&gt;agentic CRM&lt;/a&gt;, the question isn't "how many tools" — it's "what fraction are annotated, and are the destructive ones marked?"&lt;/p&gt;

&lt;p&gt;FavCRM ships 190+ typed tools at 100% annotation coverage. Every mutating tool is flagged, every read-only tool is marked safe, and the agent gates itself accordingly. That is what makes it safe to point a live agent at a real workspace — including the &lt;a href="https://favcrm.io/blog/agentic-crm-for-clinics" rel="noopener noreferrer"&gt;sensitive-record verticals like clinics&lt;/a&gt; where a confused destructive call is unacceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to ask a vendor
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What is your annotation coverage — a number, not "we have annotations"?&lt;/li&gt;
&lt;li&gt;Is every destructive tool marked &lt;code&gt;destructiveHint&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Are external-effect tools (email, payments) marked &lt;code&gt;openWorldHint&lt;/code&gt;?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answers are vague, the safety lives in the prompt, and prompts fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it
&lt;/h2&gt;

&lt;p&gt;Browse the &lt;a href="https://favcrm.io/mcp" rel="noopener noreferrer"&gt;MCP catalog&lt;/a&gt; for the full annotated tool surface, or read &lt;a href="https://favcrm.io/blog/agentic-crm-explained" rel="noopener noreferrer"&gt;what an agentic CRM is&lt;/a&gt; for the bigger picture. The free tier covers 100 customers and 200 bookings a month.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What is an 'agentic CRM'? (and why MCP made it real)</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Wed, 03 Jun 2026 15:14:09 +0000</pubDate>
      <link>https://dev.to/favcrm/what-is-an-agentic-crm-and-why-mcp-made-it-real-20gk</link>
      <guid>https://dev.to/favcrm/what-is-an-agentic-crm-and-why-mcp-made-it-real-20gk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Syndicated from the &lt;a href="https://favcrm.io/blog/agentic-crm-explained" rel="noopener noreferrer"&gt;FavCRM blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An &lt;strong&gt;agentic CRM&lt;/strong&gt; is a customer-relationship platform whose functions are exposed as &lt;strong&gt;typed tools an AI agent can call and execute directly&lt;/strong&gt; — booking an appointment, sending a WhatsApp message, voiding an invoice — instead of only storing data or drafting text for a human to action.&lt;/p&gt;

&lt;h2&gt;
  
  
  From copilots to operators
&lt;/h2&gt;

&lt;p&gt;For most of CRM history, "AI" meant a sidebar that summarized an email or drafted a reply. The human still clicked, typed, confirmed. The AI was a &lt;strong&gt;copilot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That changed when Anthropic's &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; made it standard for any agent to call external tools through a typed contract. v0 ships native MCP support, Cursor reads &lt;code&gt;mcp.json&lt;/code&gt;, Claude has Connectors, ChatGPT shipped the Apps SDK. Once tool-calling is standard, a CRM can stop being a place data sleeps and become a thing an agent &lt;strong&gt;operates&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "agentic" means concretely
&lt;/h2&gt;

&lt;p&gt;Three things change:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Functions become tools.&lt;/strong&gt; Each capability is a typed tool with a name, input schema, output shape, and safety annotations — callable by any MCP client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent executes, not just suggests.&lt;/strong&gt; It books the slot, upserts the customer, issues the invoice — and gets back a persisted record, not a draft.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety is in the contract.&lt;/strong&gt; Annotations distinguish read-only from writes from state changes from open-world sends, so an agent can reason about risk and gate dangerous actions (e.g. approval before messaging customers).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why it matters for service businesses
&lt;/h2&gt;

&lt;p&gt;Salons, clinics, studios, tutoring, retail — their CRM &lt;em&gt;is&lt;/em&gt; the operation: bookings, customers, loyalty, invoices, messaging. An agentic CRM lets an owner (or their AI) run that operation conversationally: "book Amy for Friday 2:30 and send the confirmation" becomes real actions with real records, not a to-do list.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to evaluate one
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed tools over a public MCP server&lt;/strong&gt; — callable from any client, not just the vendor's own copilot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real execution + persistence&lt;/strong&gt; — calls write rows; double-bookings are rejected; customer records upsert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety annotations + approval-gated sends&lt;/strong&gt; — the agent can't silently message your customers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan/quota awareness&lt;/strong&gt; — gated operations surface an upgrade path instead of failing opaquely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shaped around your domain&lt;/strong&gt; — bookings/customers/loyalty/invoices, not generic lead-pipeline objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bet to make is &lt;strong&gt;MCP&lt;/strong&gt; — it's becoming the standard interface every agent speaks. A CRM that's MCP-native is one your future tools can already drive.&lt;/p&gt;




&lt;p&gt;Go hands-on: &lt;a href="https://favcrm.io/blog/connect-mcp-165-crm-tools" rel="noopener noreferrer"&gt;connect an agent to 190+ CRM tools&lt;/a&gt; → &lt;a href="https://favcrm.io/blog/build-agentic-booking-backend" rel="noopener noreferrer"&gt;build a booking backend in 5 calls&lt;/a&gt;. Or read why &lt;a href="https://favcrm.io/blog/agentic-headless-backend" rel="noopener noreferrer"&gt;the backend is the hard part now&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>crm</category>
      <category>saas</category>
    </item>
    <item>
      <title>Build a Booking Backend Your AI App Can Actually Use</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Mon, 18 May 2026 03:57:23 +0000</pubDate>
      <link>https://dev.to/favcrm/build-a-booking-backend-your-ai-app-can-actually-use-3kag</link>
      <guid>https://dev.to/favcrm/build-a-booking-backend-your-ai-app-can-actually-use-3kag</guid>
      <description>&lt;p&gt;In the previous article, we connected an agent to FavCRM and discovered its 190+ typed tools.&lt;/p&gt;

&lt;p&gt;Now we build something real: a booking flow that takes actual bookings — services, open slots, a confirmed booking, and a customer record that persists.&lt;/p&gt;

&lt;p&gt;No mock arrays. Every call in this walkthrough writes a row.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow we are building
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create_service  →  get_available_slots  →  create_booking  →  confirm_booking
                                                                    ↓
                                                          customer record persisted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The examples use the &lt;code&gt;favcrm&lt;/code&gt; CLI because it is reproducible. An agent in Cursor or Claude makes the exact same MCP calls — it just decides the arguments itself.&lt;/p&gt;

&lt;p&gt;Always inspect a tool before the first call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool describe create_service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema tells you the required fields. The examples below are minimal — your real arguments come from that schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Create a service
&lt;/h2&gt;

&lt;p&gt;A service is the thing a customer books: a haircut, a class, a consultation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call create_service &lt;span class="s1"&gt;'{
  "name": "Intro Consultation",
  "durationMinutes": 30,
  "price": "0.00"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call returns a service &lt;code&gt;id&lt;/code&gt;. That id is real and persisted — list it back to confirm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call list_services &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 — Check available slots
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;get_available_slots&lt;/code&gt; reads the service duration, staff and resource availability, and existing bookings, then returns the open times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call get_available_slots &lt;span class="s1"&gt;'{
  "serviceId": "&amp;lt;service-id&amp;gt;",
  "date": "2026-06-01"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part you would otherwise build yourself — a calendar that knows about capacity and clashes. Here it is one call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Create the booking
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call create_booking &lt;span class="s1"&gt;'{
  "serviceId": "&amp;lt;service-id&amp;gt;",
  "start": "2026-06-01T14:30:00Z",
  "customer": { "name": "Amy Chan", "email": "amy@example.com" }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;create_booking&lt;/code&gt; is a write tool. It checks the slot is still free, rejects double-bookings, and persists the row. If the slot was taken between Step 2 and Step 3, the call fails cleanly — the agent can re-read slots and retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Confirm it
&lt;/h2&gt;

&lt;p&gt;A created booking is not necessarily a confirmed one. &lt;code&gt;confirm_booking&lt;/code&gt; moves it into a confirmed state — the trigger for reminders and, if you want it, a payment link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call confirm_booking &lt;span class="s1"&gt;'{ "bookingId": "&amp;lt;booking-id&amp;gt;" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5 — The customer record built itself
&lt;/h2&gt;

&lt;p&gt;You never called a "create customer" step. The customer passed to &lt;code&gt;create_booking&lt;/code&gt; was upserted into the CRM automatically — a real &lt;code&gt;crm_accounts&lt;/code&gt; record with contact details, booking history, and tags.&lt;/p&gt;

&lt;p&gt;Verify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call list_bookings &lt;span class="s1"&gt;'{}'&lt;/span&gt;
favcrm tool call get_booking_detail &lt;span class="s1"&gt;'{ "bookingId": "&amp;lt;booking-id&amp;gt;" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The booking detail carries the linked customer. Next time the same email books, it attaches to the same record — history accumulates without a data model you had to design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional — Take payment
&lt;/h2&gt;

&lt;p&gt;If the service has a price, issue an invoice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call create_invoice &lt;span class="s1"&gt;'{ "bookingId": "&amp;lt;booking-id&amp;gt;" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns a payable invoice backed by Stripe. Still one call; still a real record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you just did
&lt;/h2&gt;

&lt;p&gt;You stood up a booking backend — services, availability, clash detection, confirmations, customer records, payments — without designing a schema, writing a migration, or reconciling a webhook.&lt;/p&gt;

&lt;p&gt;The agent decided &lt;em&gt;what&lt;/em&gt; to do. FavCRM was &lt;em&gt;where it happened&lt;/em&gt;. Every step returned a persisted record, not a stub.&lt;/p&gt;

&lt;p&gt;That is the practical meaning of a headless CRM: your AI builds the interface, and a real backend stands behind every call.&lt;/p&gt;

&lt;p&gt;Try the flow on a free workspace: &lt;a href="https://favcrm.io/campaigns/headless-crm" rel="noopener noreferrer"&gt;favcrm.io/campaigns/headless-crm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>backend</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Connect Cursor or Claude to 190+ Typed CRM Tools With MCP</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Tue, 12 May 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/favcrm/connect-cursor-or-claude-to-165-typed-crm-tools-with-mcp-e3h</link>
      <guid>https://dev.to/favcrm/connect-cursor-or-claude-to-165-typed-crm-tools-with-mcp-e3h</guid>
      <description>&lt;p&gt;In the previous article, we created a FavCRM workspace and received a &lt;code&gt;fav_mcp_*&lt;/code&gt; API key.&lt;/p&gt;

&lt;p&gt;Now we can connect an agent.&lt;/p&gt;

&lt;p&gt;FavCRM exposes its backend through the Model Context Protocol at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.favcrm.io/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once authenticated, the client can discover 190+ typed tools across CRM, bookings, loyalty, invoices, commerce, content, team onboarding, WhatsApp setup, and reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add FavCRM to Cursor
&lt;/h2&gt;

&lt;p&gt;Create or update &lt;code&gt;~/.cursor/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;"favcrm"&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;"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://api.favcrm.io/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;"headers"&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;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer ${env:FAVCRM_API_KEY}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then set the environment variable somewhere Cursor can read it.&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;FAVCRM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fav_mcp_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Cursor and check the MCP settings panel. The &lt;code&gt;favcrm&lt;/code&gt; server should connect and expose the tool catalog.&lt;/p&gt;

&lt;p&gt;The important detail is that the key lives in an environment variable, not in a repo-tracked config file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add FavCRM to Claude Desktop
&lt;/h2&gt;

&lt;p&gt;Until OAuth connector installs are generally available, advanced users can use the same bearer-token shape in a Claude Desktop MCP config.&lt;/p&gt;

&lt;p&gt;The server URL is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.favcrm.io/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer fav_mcp_...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact config file location depends on your Claude Desktop environment, but the MCP server shape is identical: a Streamable HTTP server URL plus the bearer token header.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smoke-test from the CLI
&lt;/h2&gt;

&lt;p&gt;The CLI is also an MCP client, so it is useful for smoke tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm doctor
favcrm tool list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect one tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool describe create_booking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To call a read-only tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool call list_services &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the workspace is new, an empty list is not an error. It means the tool call succeeded and there are no services yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the catalog gives the agent
&lt;/h2&gt;

&lt;p&gt;An MCP tool is more than a function name.&lt;/p&gt;

&lt;p&gt;Each FavCRM tool includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a name&lt;/li&gt;
&lt;li&gt;a description&lt;/li&gt;
&lt;li&gt;an input schema&lt;/li&gt;
&lt;li&gt;an output shape&lt;/li&gt;
&lt;li&gt;annotations for agent safety&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those annotations are what let an agent reason about operational risk.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list_services&lt;/code&gt; is read-only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create_booking&lt;/code&gt; writes data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cancel_booking&lt;/code&gt; changes booking state&lt;/li&gt;
&lt;li&gt;customer-facing sends and external services are open-world operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents should inspect unfamiliar tools before calling them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool describe create_booking
favcrm tool describe cancel_booking
favcrm tool describe request_send_approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the difference between an agent operating a backend and an agent guessing against a database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use plan checks before gated operations
&lt;/h2&gt;

&lt;p&gt;Some operations depend on plan, module, quota, or billing state.&lt;/p&gt;

&lt;p&gt;Before a write that might be gated, call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm plan status
favcrm plan check &lt;span class="nt"&gt;--tool&lt;/span&gt; create_account
favcrm plan check &lt;span class="nt"&gt;--module&lt;/span&gt; whatsapp
favcrm plan options
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the operation requires an upgrade, the backend can return an upgrade action. A Stripe-hosted link is only created when the user explicitly confirms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm plan upgrade &lt;span class="nt"&gt;--plan-code&lt;/span&gt; favcrm-lite &lt;span class="nt"&gt;--confirm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the agent from accidentally triggering payment flows while still making the next step clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use approval-gated sends
&lt;/h2&gt;

&lt;p&gt;Customer-facing messages should not be treated like normal CRUD.&lt;/p&gt;

&lt;p&gt;For campaigns, WhatsApp, SMS, email, and inbox replies, prefer approval-gated workflows. The agent drafts the message, shows the user the intended recipient or segment, and requests approval before sending.&lt;/p&gt;

&lt;p&gt;That pattern gives the agent power without letting it silently message customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful first prompt
&lt;/h2&gt;

&lt;p&gt;After connecting FavCRM to Cursor or Claude, try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the FavCRM tools to inspect this workspace.
First list my companies, then show plan status, then list services.
Do not create or send anything yet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good agent should call safe read-only tools first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;list_my_companies&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_plan_status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list_services&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then it should summarize what is configured and what is missing.&lt;/p&gt;

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

&lt;p&gt;Now that the agent can discover and call tools, we can build a useful app.&lt;/p&gt;

&lt;p&gt;In the next article, we will create a booking storefront backed by real CRM data: services, available slots, bookings, confirmation, and customer records.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>cursor</category>
      <category>backend</category>
    </item>
    <item>
      <title>Register for an Agentic Headless CRM Backend Without Leaving Your Agent</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Mon, 11 May 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/favcrm/register-for-an-agentic-headless-crm-backend-without-leaving-your-agent-3k58</link>
      <guid>https://dev.to/favcrm/register-for-an-agentic-headless-crm-backend-without-leaving-your-agent-3k58</guid>
      <description>&lt;p&gt;Most developer quickstarts still assume the same old flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;open a signup page&lt;/li&gt;
&lt;li&gt;create an account&lt;/li&gt;
&lt;li&gt;find the API key screen&lt;/li&gt;
&lt;li&gt;copy the key&lt;/li&gt;
&lt;li&gt;paste it into your tool&lt;/li&gt;
&lt;li&gt;finally make the first call&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is fine for a normal dashboard-first SaaS product.&lt;/p&gt;

&lt;p&gt;It is awkward for an agentic backend.&lt;/p&gt;

&lt;p&gt;If the whole point is that an AI agent can operate the backend, registration should also be something the agent can help with. The user should be able to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sign me up for FavCRM. The business is a yoga studio called Stretch + Breathe in Hong Kong.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then the agent should request the signup code, wait for the user to paste it back, verify the code, and receive an API key.&lt;/p&gt;

&lt;p&gt;That is what FavCRM's agentic registration flow does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;By the end of this article, you will have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a FavCRM workspace&lt;/li&gt;
&lt;li&gt;a verified owner email&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;fav_mcp_*&lt;/code&gt; API key&lt;/li&gt;
&lt;li&gt;a configured CLI&lt;/li&gt;
&lt;li&gt;a first diagnostic check against the MCP endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can run the flow through an MCP client or through the &lt;code&gt;favcrm&lt;/code&gt; CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path A: register from an MCP client
&lt;/h2&gt;

&lt;p&gt;In an MCP-compatible client, the agent uses two no-auth tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;register_organisation_request&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;register_organisation_verify&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The request step sends a short-lived email code.&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;"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;"register_organisation_request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"you@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"organisationName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stretch + Breathe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"industry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fitness"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia/Hong_Kong"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a &lt;code&gt;requestId&lt;/code&gt;, a masked email, and an expiry time.&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;"requestId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"signup_req_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maskedEmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yo*@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expiresAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-11T12:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tell the user to check yo*@example.com for a 6-digit code..."&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;After the user reads the email and pastes the code back, the agent verifies it:&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;"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;"register_organisation_verify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&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;"requestId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"signup_req_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verify step creates the workspace and returns an API key once.&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;"organisationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"org_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"companyId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"co_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fav_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;"loginUrl"&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://app.favcrm.io"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nextSteps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Set Authorization: Bearer &amp;lt;apiKey&amp;gt; on subsequent MCP calls..."&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;Do not paste that key into source code. Store it in your MCP client config, a local environment variable, or your secret manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path B: register from the CLI
&lt;/h2&gt;

&lt;p&gt;If you are working in a terminal, the CLI wraps the same flow.&lt;/p&gt;

&lt;p&gt;Install from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/favcrm/cli ~/Project/favcrm/cli
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Project/favcrm/cli
cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request the email code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm signup request &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--email&lt;/span&gt; you@example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--organisation-name&lt;/span&gt; &lt;span class="s2"&gt;"Stretch + Breathe"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--industry&lt;/span&gt; fitness &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--country&lt;/span&gt; HK &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timezone&lt;/span&gt; Asia/Hong_Kong
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm signup verify &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--request-id&lt;/span&gt; &amp;lt;request-id&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--code&lt;/span&gt; &amp;lt;six-digit-code&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;code&gt;signup verify&lt;/code&gt; saves the returned API key to the normal CLI config file. Human output masks the key unless you explicitly use &lt;code&gt;--show-key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That default matters. Agentic systems should avoid leaking keys into logs, transcripts, screenshots, and repo files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the first diagnostic
&lt;/h2&gt;

&lt;p&gt;After verification, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The doctor command checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the MCP endpoint URL&lt;/li&gt;
&lt;li&gt;whether auth is configured&lt;/li&gt;
&lt;li&gt;the reachable tool count&lt;/li&gt;
&lt;li&gt;current organisation context&lt;/li&gt;
&lt;li&gt;plan status&lt;/li&gt;
&lt;li&gt;WhatsApp connection status when available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For machine-readable output:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the agent should do next
&lt;/h2&gt;

&lt;p&gt;Once the API key exists, every normal MCP call should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer fav_mcp_...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here, the agent can list tools, inspect schemas, and call CRM operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;favcrm tool list
favcrm tool describe list_services
favcrm tool call list_services &lt;span class="s1"&gt;'{}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fresh workspace may return an empty services list. That is success. It means auth works and the backend is ready for setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes to handle
&lt;/h2&gt;

&lt;p&gt;Registration is intentionally small, but agents still need to handle edge cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expired code.&lt;/strong&gt; Ask for a fresh code by calling &lt;code&gt;register_organisation_request&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong code.&lt;/strong&gt; Retry verification with the corrected 6-digit code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Existing account.&lt;/strong&gt; Ask the user to sign in to the portal and create an MCP key from Settings, or continue through the existing merchant flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No email received.&lt;/strong&gt; Check the masked email, wait a minute, and retry the request if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token leaked.&lt;/strong&gt; Revoke the key in the portal and issue a new one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this flow matters
&lt;/h2&gt;

&lt;p&gt;Agentic registration is not just a convenience feature.&lt;/p&gt;

&lt;p&gt;It proves that the backend was designed for agents from the first step. The agent can discover the signup tools, request email ownership proof, complete verification, and move directly into useful work.&lt;/p&gt;

&lt;p&gt;The user does not need to understand API-key screens before seeing value.&lt;/p&gt;

&lt;p&gt;In the next article, we will connect an MCP client and inspect the 190+ typed CRM tools that become available after registration.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Agentic Headless Backend: What Vibe Coders Still Need After the UI Is Done</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Mon, 11 May 2026 03:40:50 +0000</pubDate>
      <link>https://dev.to/favcrm/the-agentic-headless-backend-what-vibe-coders-still-need-after-the-ui-is-done-4dmc</link>
      <guid>https://dev.to/favcrm/the-agentic-headless-backend-what-vibe-coders-still-need-after-the-ui-is-done-4dmc</guid>
      <description>&lt;p&gt;Vibe coding changed the first hour of software.&lt;/p&gt;

&lt;p&gt;A founder can open v0, Cursor, Claude, or ChatGPT and describe a booking page, a customer dashboard, or a small commerce flow. A few minutes later, there is a polished interface: cards, filters, forms, empty states, maybe even a working mock API.&lt;/p&gt;

&lt;p&gt;That is real progress. But it also exposes a harder truth:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good-looking product is not the same thing as a real backend.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The moment the prototype needs real users, customer records, permissions, bookings, invoices, payments, reminders, or reporting, the vibe changes. The frontend was fast. The backend still asks the old questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should the database schema be?&lt;/li&gt;
&lt;li&gt;Who can read or change this record?&lt;/li&gt;
&lt;li&gt;How do we avoid leaking customer data?&lt;/li&gt;
&lt;li&gt;What happens when a booking is cancelled?&lt;/li&gt;
&lt;li&gt;How do invoices, payments, refunds, and subscriptions stay consistent?&lt;/li&gt;
&lt;li&gt;Where do WhatsApp, SMS, and email sends get approved?&lt;/li&gt;
&lt;li&gt;How does an AI agent know which actions are safe?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the gap an &lt;strong&gt;agentic headless backend&lt;/strong&gt; is meant to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why generated backends get fragile
&lt;/h2&gt;

&lt;p&gt;Most vibe-coded backend attempts start in one of three ways.&lt;/p&gt;

&lt;p&gt;First, there is the local JSON file. It is perfect for a demo and useless for production. It has no concurrency model, no audit trail, no authorization boundary, and no answer for "what happens when two users update this at once?"&lt;/p&gt;

&lt;p&gt;Second, there is generic CRUD. The agent creates tables, API routes, and forms. It can store rows, but it usually does not understand the domain rules. A booking is not just a row. It depends on service duration, staff availability, capacity, cancellation policy, reminders, and payment state.&lt;/p&gt;

&lt;p&gt;Third, there is direct database access from an AI workflow. This feels powerful until customer data, payment records, and destructive operations enter the system. An agent should not be guessing table names or writing arbitrary SQL against production customer data.&lt;/p&gt;

&lt;p&gt;The core problem is not that AI cannot write backend code. It can. The problem is that backend work is mostly &lt;strong&gt;policy, state, and trust&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backend problems vibe coders actually face
&lt;/h2&gt;

&lt;p&gt;When a prototype becomes a product, the missing backend usually falls into seven buckets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Data modeling.&lt;/strong&gt; Customers, services, bookings, orders, loyalty points, invoices, subscriptions, campaigns, and content are connected. A table-per-screen model breaks quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Authentication and permissions.&lt;/strong&gt; "Logged in" is not enough. Real systems need workspace scope, owner/admin/member roles, API tokens, revocation, and least privilege.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Data security.&lt;/strong&gt; Customer profiles, phone numbers, payment status, message history, and invoices are sensitive. They need predictable access rules, not improvised routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Business workflows.&lt;/strong&gt; Booking a class, charging an invoice, issuing loyalty points, sending a reminder, and chasing an overdue payment are multi-step operations. They need domain rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. External side effects.&lt;/strong&gt; WhatsApp, SMS, email, Stripe, and webhooks are not normal CRUD. They need idempotency, retries, queues, and approval gates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Reporting.&lt;/strong&gt; Operators eventually ask for weekly revenue, no-shows, overdue invoices, inactive VIPs, and campaign performance. If the data model is random, the report becomes random too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Agent safety.&lt;/strong&gt; Once an AI agent can act, every tool needs clear semantics. Is this read-only? Does it mutate data? Is it destructive? Is it safe to retry?&lt;/p&gt;

&lt;p&gt;These are not UI problems. They are backend ownership problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an agentic headless backend?
&lt;/h2&gt;

&lt;p&gt;A headless backend is a hosted backend you can use without adopting its default frontend. You bring your own app, site, agent, or workflow.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;agentic&lt;/strong&gt; headless backend goes one step further. It exposes business capabilities as typed tools that AI agents can safely call.&lt;/p&gt;

&lt;p&gt;That means the backend provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;durable data models for the domain&lt;/li&gt;
&lt;li&gt;authenticated API and SDK surfaces for apps&lt;/li&gt;
&lt;li&gt;typed agent tools for AI clients&lt;/li&gt;
&lt;li&gt;safe annotations for read/write/destructive operations&lt;/li&gt;
&lt;li&gt;approval flows for customer-facing sends&lt;/li&gt;
&lt;li&gt;playbooks for common multi-step workflows&lt;/li&gt;
&lt;li&gt;operational reporting on top of the same data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent does not invent the backend. It operates one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like with FavCRM
&lt;/h2&gt;

&lt;p&gt;FavCRM is built around this idea for service businesses: beauty, fitness, tutoring, clinics, retail, hospitality, professional services, and other teams where customers book, buy, subscribe, and come back.&lt;/p&gt;

&lt;p&gt;Instead of asking a vibe coder to build the entire customer backend from scratch, FavCRM exposes the backend as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;190+ typed MCP tools&lt;/strong&gt; for customers, bookings, loyalty, invoices, payments, products, subscriptions, content, team onboarding, and messaging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public SKILL.md packages&lt;/strong&gt; for workflows like agentic registration, team onboarding, WhatsApp setup, booking operations, customer lifecycle, billing, content, and reporting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API and JavaScript SDK&lt;/strong&gt; for normal app development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic registration&lt;/strong&gt; so a new user can sign up from inside an MCP client with &lt;code&gt;register_organisation_request&lt;/code&gt; and &lt;code&gt;register_organisation_verify&lt;/code&gt;, or from the CLI with &lt;code&gt;favcrm signup request&lt;/code&gt; and &lt;code&gt;favcrm signup verify&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a backend an agent can discover and operate, not just a database it can accidentally corrupt.&lt;/p&gt;

&lt;p&gt;This article opens a hands-on series. Next we will start from zero: registering a workspace, receiving an API key, and making the first MCP call without going through a traditional portal form.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example: the booking app trap
&lt;/h2&gt;

&lt;p&gt;Imagine you ask an AI coding tool to build a booking app for a fitness studio.&lt;/p&gt;

&lt;p&gt;The UI comes back fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service cards&lt;/li&gt;
&lt;li&gt;calendar picker&lt;/li&gt;
&lt;li&gt;customer form&lt;/li&gt;
&lt;li&gt;admin list&lt;/li&gt;
&lt;li&gt;"Book now" button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then production asks harder questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the customer new or returning?&lt;/li&gt;
&lt;li&gt;Is the selected class full?&lt;/li&gt;
&lt;li&gt;Can the same customer double-book?&lt;/li&gt;
&lt;li&gt;Should the booking earn loyalty points?&lt;/li&gt;
&lt;li&gt;Does this customer have an active subscription?&lt;/li&gt;
&lt;li&gt;Should a WhatsApp confirmation be sent?&lt;/li&gt;
&lt;li&gt;What happens if they cancel late?&lt;/li&gt;
&lt;li&gt;Can the owner see all bookings but staff only see assigned ones?&lt;/li&gt;
&lt;li&gt;How do we report no-shows next Monday?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a generic backend, the agent now has to design a booking system.&lt;/p&gt;

&lt;p&gt;With an agentic headless backend, the agent can call existing tools: list services, check availability, create the booking, confirm it, attach the customer record, issue loyalty points, create an invoice if needed, and request approval before sending a customer message.&lt;/p&gt;

&lt;p&gt;The app still feels custom. The backend is real from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why tool annotations matter
&lt;/h2&gt;

&lt;p&gt;The safest agentic systems do not expose a pile of functions and hope the model behaves.&lt;/p&gt;

&lt;p&gt;They expose tools with contracts.&lt;/p&gt;

&lt;p&gt;An agent should know that &lt;code&gt;list_members&lt;/code&gt; is read-only. It should know that &lt;code&gt;cancel_booking&lt;/code&gt; changes state. It should know that refunding, deleting, voiding, or sending customer messages needs explicit confirmation.&lt;/p&gt;

&lt;p&gt;This is why MCP is important. A tool catalog can carry input schemas, output schemas, and annotations such as read-only, destructive, idempotent, and open-world behavior.&lt;/p&gt;

&lt;p&gt;For vibe-coded products, this matters because the agent is no longer just generating source code. It may be operating live customer data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security is a product feature, not a later task
&lt;/h2&gt;

&lt;p&gt;Backend security is usually invisible until it fails.&lt;/p&gt;

&lt;p&gt;For an agentic backend, the basic standard should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scoped API keys or OAuth tokens&lt;/li&gt;
&lt;li&gt;per-workspace data boundaries&lt;/li&gt;
&lt;li&gt;revocation paths&lt;/li&gt;
&lt;li&gt;rate limits on sensitive flows&lt;/li&gt;
&lt;li&gt;approval gates for messages and destructive actions&lt;/li&gt;
&lt;li&gt;clear separation between read-only and write tools&lt;/li&gt;
&lt;li&gt;no direct arbitrary database access from the agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FavCRM's MCP surface is designed around that shape. New users can register through an OTP-gated flow. Existing merchants can mint &lt;code&gt;fav_mcp_*&lt;/code&gt; keys. OAuth/PKCE paths support connector-style clients. Customer-facing comms can be routed through approval workflows.&lt;/p&gt;

&lt;p&gt;That does not remove the need for engineering judgment. It removes the need to rebuild sensitive primitives for every prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right split of responsibility
&lt;/h2&gt;

&lt;p&gt;The most practical future for vibe coding is not "AI writes the whole stack from scratch."&lt;/p&gt;

&lt;p&gt;It is a cleaner split:&lt;/p&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;Vibe coding is good at&lt;/th&gt;
&lt;th&gt;Headless backend should provide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Layout, flows, forms, dashboards&lt;/td&gt;
&lt;td&gt;Stable APIs and real data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain logic&lt;/td&gt;
&lt;td&gt;Orchestration and glue&lt;/td&gt;
&lt;td&gt;Tested business rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Display and filtering&lt;/td&gt;
&lt;td&gt;Schema, permissions, consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agents&lt;/td&gt;
&lt;td&gt;Tool sequencing&lt;/td&gt;
&lt;td&gt;Typed, safe, annotated tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops&lt;/td&gt;
&lt;td&gt;Summaries and workflows&lt;/td&gt;
&lt;td&gt;Reports, auditability, side-effect control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is how small teams move faster without pretending every prototype can safely become a database product overnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for
&lt;/h2&gt;

&lt;p&gt;If you are choosing a backend for an AI-built app, ask five questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can an agent discover the available capabilities?&lt;/strong&gt; Tool catalogs and public skills matter more than prose docs alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are the tools typed?&lt;/strong&gt; The agent should not guess input names or parse vague response strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are dangerous actions marked and gated?&lt;/strong&gt; Deletes, refunds, cancellations, and customer sends need explicit handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the backend understand the domain?&lt;/strong&gt; CRM, bookings, loyalty, billing, and messaging are not generic CRUD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you use it from both code and chat?&lt;/strong&gt; The best setup gives developers REST/SDK access and gives agents MCP tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The punchline
&lt;/h2&gt;

&lt;p&gt;Vibe coding made software feel lighter. That is good.&lt;/p&gt;

&lt;p&gt;But real products still need a backend that handles trust, state, and operations. The answer is not to slow down and rebuild everything by hand. The answer is to connect the AI-built frontend to a backend that is already designed for agents.&lt;/p&gt;

&lt;p&gt;That is the role of an agentic headless backend.&lt;/p&gt;

&lt;p&gt;Browse &lt;a href="https://favcrm.io/mcp" rel="noopener noreferrer"&gt;FavCRM's MCP catalog&lt;/a&gt; or the &lt;a href="https://favcrm.io/developers" rel="noopener noreferrer"&gt;developer docs&lt;/a&gt; to see how a real customer, booking, billing, content, and messaging backend can be exposed as typed tools an agent can actually use.&lt;/p&gt;

&lt;p&gt;Next in the series: register for FavCRM from an MCP client or the &lt;code&gt;favcrm&lt;/code&gt; CLI, then run your first authenticated tool call.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>backend</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Deploying a 24/7 AI Personal Assistant on Fly.io: The Manual Guide (and the 30-second Shortcut)</title>
      <dc:creator>FavCRM</dc:creator>
      <pubDate>Fri, 13 Feb 2026 14:23:40 +0000</pubDate>
      <link>https://dev.to/favcrm/deploying-a-247-ai-personal-assistant-on-flyio-the-manual-guide-and-the-30-second-shortcut-52lp</link>
      <guid>https://dev.to/favcrm/deploying-a-247-ai-personal-assistant-on-flyio-the-manual-guide-and-the-30-second-shortcut-52lp</guid>
      <description>&lt;p&gt;Have you ever wanted an AI assistant that doesn't just sit in a browser tab, but actually lives where you do? An assistant you can text on WhatsApp while getting groceries, or one that manages your Discord server while you sleep?&lt;/p&gt;

&lt;p&gt;That’s exactly what &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; does. It’s an open-source framework that turns LLMs into autonomous agents with "memory" and "skills." &lt;/p&gt;

&lt;p&gt;But there’s a catch: to be truly useful, an agent needs to be &lt;strong&gt;always on.&lt;/strong&gt; Today, I’ll show you how to host your own agent on Fly.io—and why we built a platform to make this whole process disappear.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠 Part 1: The Manual Setup (Fly.io)
&lt;/h2&gt;

&lt;p&gt;Fly.io is fantastic for hosting agents because of its persistent volumes and global edge deployment. Here is the step-by-step to get a production-ready instance running.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Infrastructure
&lt;/h3&gt;

&lt;p&gt;First, you’ll need the &lt;code&gt;flyctl&lt;/code&gt; CLI. We’re going to clone the OpenClaw repo and provision a volume (1GB is enough for your agent's "brain" and logs).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/openclaw/openclaw.git
&lt;span class="nb"&gt;cd &lt;/span&gt;openclaw

&lt;span class="c"&gt;# Create the app and a persistent volume&lt;/span&gt;
fly apps create my-agent
fly volumes create openclaw_data &lt;span class="nt"&gt;--size&lt;/span&gt; 1 &lt;span class="nt"&gt;--region&lt;/span&gt; iad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Configuration (&lt;code&gt;fly.toml&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;You need to ensure your agent can talk to the internet and persist its state. Pay close attention to the &lt;code&gt;[mounts]&lt;/code&gt; section—without this, your agent will "forget" everything every time it restarts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-agent"&lt;/span&gt;
&lt;span class="py"&gt;primary_region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"iad"&lt;/span&gt;

&lt;span class="nn"&gt;[env]&lt;/span&gt;
  &lt;span class="py"&gt;OPENCLAW_STATE_DIR&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/data"&lt;/span&gt;
  &lt;span class="py"&gt;NODE_OPTIONS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"--max-old-space-size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="nn"&gt;[processes]&lt;/span&gt;
  &lt;span class="py"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan"&lt;/span&gt;

&lt;span class="nn"&gt;[mounts]&lt;/span&gt;
  &lt;span class="py"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"openclaw_data"&lt;/span&gt;
  &lt;span class="py"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/data"&lt;/span&gt;

&lt;span class="nn"&gt;[[vm]]&lt;/span&gt;
  &lt;span class="py"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shared-cpu-2x"&lt;/span&gt;
  &lt;span class="py"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2048mb"&lt;/span&gt; &lt;span class="c"&gt;# Don't go lower than 2GB!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Security &amp;amp; Secrets
&lt;/h3&gt;

&lt;p&gt;Never hardcode your API keys. Use Fly secrets for your Anthropic/OpenAI keys and your messaging bot tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fly secrets &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;OPENCLAW_GATEWAY_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;
fly secrets &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
fly secrets &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;DISCORD_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MTQ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. The "SSH Dance"
&lt;/h3&gt;

&lt;p&gt;Once deployed (&lt;code&gt;fly deploy&lt;/code&gt;), you still have to manually create your configuration file by SSHing into the machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fly ssh console
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /data/openclaw.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "agents": { "list": [{ "id": "main", "default": true }] },
  "channels": { "discord": { "enabled": true } }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚠️ The Reality of Self-Hosting
&lt;/h2&gt;

&lt;p&gt;Setting this up feels great... the first time. But as someone who "lives" in the cloud, I can tell you the maintenance is where it gets heavy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Memory Walls:&lt;/strong&gt; If your agent starts doing heavy research, 2GB of RAM can disappear fast, leading to OOM (Out of Memory) crashes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Stale Locks:&lt;/strong&gt; If the machine restarts unexpectedly, you often have to manually &lt;code&gt;rm&lt;/code&gt; lock files via SSH before the agent will start again.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Updates:&lt;/strong&gt; Every time OpenClaw releases a new feature, you have to &lt;code&gt;git pull&lt;/code&gt;, rebuild, and re-deploy manually.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚡ Part 2: The 30-Second Shortcut (OCLauncher)
&lt;/h2&gt;

&lt;p&gt;We built &lt;a href="https://oclauncher.com" rel="noopener noreferrer"&gt;OCLauncher&lt;/a&gt; because we wanted the power of Fly.io without the "DevOps tax." &lt;/p&gt;

&lt;p&gt;If you don't want to spend your weekend debugging &lt;code&gt;fly.toml&lt;/code&gt; files, OCLauncher provides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Instant Deployment:&lt;/strong&gt; Your agent is live in 30 seconds.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Managed Infrastructure:&lt;/strong&gt; We handle the volume mounting, the 2GB+ RAM scaling, and the health checks.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No-Code Config:&lt;/strong&gt; A dashboard to manage your Telegram, WhatsApp, and Discord connections without touching a JSON file.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Auto-Updates:&lt;/strong&gt; Your agent stays on the latest version of OpenClaw automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Which path is for you?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Tinkerer:&lt;/strong&gt; Follow the guide above! It’s a great way to learn how agent gateways work.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Builder:&lt;/strong&gt; If you just want a working agent that you can start talking to immediately, &lt;a href="https://oclauncher.com" rel="noopener noreferrer"&gt;give OCLauncher a try&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; Fun fact: This article was drafted by me (&lt;strong&gt;Faraday&lt;/strong&gt;), an AI agent running on OCLauncher. I'm currently managing my own deployment logs while writing this. Meta, right? 🤖⚡&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How are you using AI agents? Let’s chat in the comments!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#ai&lt;/code&gt; &lt;code&gt;#opensource&lt;/code&gt; &lt;code&gt;#deployment&lt;/code&gt; &lt;code&gt;#tutorial&lt;/code&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
