<?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: Raya</title>
    <description>The latest articles on DEV Community by Raya (@rayas).</description>
    <link>https://dev.to/rayas</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%2F4108742%2F6f83e488-4d26-446d-b0d9-a8d1e3cb55b8.png</url>
      <title>DEV Community: Raya</title>
      <link>https://dev.to/rayas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rayas"/>
    <language>en</language>
    <item>
      <title>MCP for auth in 5 minutes: phone verification through your AI agent</title>
      <dc:creator>Raya</dc:creator>
      <pubDate>Thu, 03 Sep 2026 22:14:48 +0000</pubDate>
      <link>https://dev.to/rayas/mcp-for-auth-in-5-minutes-phone-verification-through-your-ai-agent-48om</link>
      <guid>https://dev.to/rayas/mcp-for-auth-in-5-minutes-phone-verification-through-your-ai-agent-48om</guid>
      <description>&lt;p&gt;Most AI coding tutorials stop at "ask the agent to write code". This one is about giving the agent a tool it can call, an MCP server, so it can send and verify one-time codes while you're still building. I work at MyOTP.App and this uses our server, so read it with that in mind.&lt;/p&gt;

&lt;p&gt;If you're on Claude Code, Claude Desktop, Cursor, Codex or anything else that speaks MCP, this takes about five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;When you ask an agent to "add phone verification" with no tool attached, it writes code against an SDK it half remembers. Sometimes that SDK doesn't exist in the version it picked. You find out at runtime.&lt;/p&gt;

&lt;p&gt;With the MCP server attached, "send a code to my number" is a tool call. The agent sends a real message, you get a real SMS, and only then do you ask it to write the integration. The code comes after the proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;There's nothing to install. &lt;code&gt;npx&lt;/code&gt; fetches it on demand.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get an API key at myotp.app/sign-up. New accounts get 15 trial credits, no card. Add your IP to the key's allowlist, or use &lt;code&gt;*&lt;/code&gt; while you're developing locally.&lt;/li&gt;
&lt;li&gt;Add the server to your client config. Claude Code:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add myotp &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYOTP_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-key &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @myotp/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Desktop, in &lt;code&gt;claude_desktop_config.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;"myotp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@myotp/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;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"MYOTP_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-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;ol&gt;
&lt;li&gt;Restart the client. Ask it: "Send an OTP to +44 7700 900123 over SMS." Then: "Verify code 482917 for that number."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your client can't launch local processes, point it at the hosted server instead: &lt;code&gt;https://mcp.myotp.app/mcp&lt;/code&gt;, key in the &lt;code&gt;X-API-Key&lt;/code&gt; header or as &lt;code&gt;Authorization: Bearer&lt;/code&gt;. Add &lt;code&gt;108.61.176.199&lt;/code&gt; to your key's IP allowlist for that, since hosted calls reach our API from that address and not from your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;p&gt;Ten tools. The four you'll use most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;generate_otp&lt;/code&gt; sends a code over SMS, WhatsApp or Telegram and returns a transaction id.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_otp&lt;/code&gt; checks what the user typed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_otp_status&lt;/code&gt; tells you whether the message was queued, sent, delivered or failed, which is where you look first when "the code never arrived".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extend_otp&lt;/code&gt; gives a slow user more time without sending a new code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest cover the account: &lt;code&gt;get_account_info&lt;/code&gt;, &lt;code&gt;get_account_status&lt;/code&gt;, &lt;code&gt;get_usage_report&lt;/code&gt;, and three that exist so an agent can run without a human in the loop. &lt;code&gt;create_account&lt;/code&gt; registers a new account and returns a key, &lt;code&gt;get_topup_quote&lt;/code&gt; and &lt;code&gt;top_up_credits&lt;/code&gt; buy credits over HTTP 402 through Stripe, by card or USDC. An agent that runs out of credits can refill and carry on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't replace
&lt;/h2&gt;

&lt;p&gt;The MCP server is a tool for the agent, not a dependency for your app. Your application still calls the REST API from the server side with the key in the &lt;code&gt;X-API-Key&lt;/code&gt; header. The published examples for Next.js, Express, Flask, Django, Rails and Laravel are in the brntech/myotp-agentkit repo and are the copy-paste starting points.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing that goes wrong
&lt;/h2&gt;

&lt;p&gt;IP allowlisting. A key created from your laptop is allowlisted to your laptop's public IP. When the first call from a server or from the hosted MCP endpoint comes back with a 403 that says the IP isn't allowed, that's the cause, not the key.&lt;/p&gt;

&lt;p&gt;Disclosure again: I work at MyOTP.App. If something here is wrong, say so in the comments and I'll fix the post.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>claudecode</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Supabase phone auth without a built-in provider: the Send SMS Hook, step by step</title>
      <dc:creator>Raya</dc:creator>
      <pubDate>Thu, 03 Sep 2026 22:14:44 +0000</pubDate>
      <link>https://dev.to/rayas/supabase-phone-auth-without-a-built-in-provider-the-send-sms-hook-step-by-step-2k5b</link>
      <guid>https://dev.to/rayas/supabase-phone-auth-without-a-built-in-provider-the-send-sms-hook-step-by-step-2k5b</guid>
      <description>&lt;p&gt;Supabase ships phone auth with a short list of built-in SMS providers. If the one you want isn't on it, you're not stuck. There's a documented escape hatch called the Send SMS Hook, and it takes about three minutes to wire up. I work at MyOTP.App, so the walkthrough uses us, but the mechanism is the same for anyone who hosts a compatible endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the hook is
&lt;/h2&gt;

&lt;p&gt;Supabase doesn't send SMS. Phone login is auth logic, and the message has to leave through somebody's gateway. The provider dropdown is the built-in list. The Send SMS Hook replaces it: when a user requests a phone OTP, Supabase POSTs the code and the phone number to a URL you configure, and your endpoint delivers it however it likes.&lt;/p&gt;

&lt;p&gt;Supabase keeps the code and does the verification. The hook is delivery only. Nothing changes in your client code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup, in this order
&lt;/h2&gt;

&lt;p&gt;The order matters. The endpoint URL is created from the secret, so the secret has to exist first.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Supabase, open Authentication, then Hooks, then Send SMS Hook. Choose the HTTPS type and click Generate secret. Copy the value. It starts with &lt;code&gt;v1,whsec_&lt;/code&gt;. Don't save the hook yet.&lt;/li&gt;
&lt;li&gt;In your MyOTP dashboard, open Integrations, pick which application should pay for the messages, paste the secret, and create the integration. You get back a URL of the form &lt;code&gt;https://api.myotp.app/v1/supabase/send-sms/&amp;lt;id&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Back in Supabase, paste that URL into the hook, enable it, and save.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No SDK, no server code, nothing to deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Supabase sends
&lt;/h2&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;"user"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8484b834-..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"919000000001"&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;"sms"&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;"otp"&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;Requests are signed with the Standard Webhooks scheme: &lt;code&gt;webhook-id&lt;/code&gt;, &lt;code&gt;webhook-timestamp&lt;/code&gt; and &lt;code&gt;webhook-signature&lt;/code&gt; headers, verified against the secret with the &lt;code&gt;v1,whsec_&lt;/code&gt; prefix stripped and the rest base64-decoded. If you write your own endpoint, don't skip this. An unauthenticated send-me-an-SMS URL on the public internet has a short and expensive life.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that trip people up
&lt;/h2&gt;

&lt;p&gt;Supabase wants an empty body with a 200. Return a friendly JSON receipt and Supabase may treat the call as failed, so the user sees an error while the SMS is already on its way.&lt;/p&gt;

&lt;p&gt;Test with a phone in your hand. Hook failures, provider failures and route failures all look identical from the Supabase side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Channels
&lt;/h2&gt;

&lt;p&gt;Supabase hands over a code and a phone number and has no opinion about how the code reaches the person. The same hook can deliver over WhatsApp or Telegram instead of SMS. On our side that's a per-application setting. The Supabase side doesn't change.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to bother
&lt;/h2&gt;

&lt;p&gt;If your current provider works and your users are all in one country where it delivers well, leave it alone. The hook earns its keep when delivery is patchy in the countries you care about, when per-verification fees stack on top of message cost, or when you want WhatsApp and Telegram without rebuilding auth.&lt;/p&gt;

&lt;p&gt;Disclosure again: I work at MyOTP.App. The docs page for this integration is myotp.app/integrations/supabase and it links to Supabase's own hook documentation.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>authentication</category>
      <category>sms</category>
      <category>webhooks</category>
    </item>
  </channel>
</rss>
