<?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: QRflows</title>
    <description>The latest articles on DEV Community by QRflows (@qrflows).</description>
    <link>https://dev.to/qrflows</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%2F3947933%2F278784b4-82c7-4b16-bacd-1e2a681ac75d.png</url>
      <title>DEV Community: QRflows</title>
      <link>https://dev.to/qrflows</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/qrflows"/>
    <language>en</language>
    <item>
      <title>How to Submit Your MCP Server to Anthropic's Connector Directory (From Someone Who Did It)</title>
      <dc:creator>QRflows</dc:creator>
      <pubDate>Wed, 03 Jun 2026 17:14:21 +0000</pubDate>
      <link>https://dev.to/qrflows/how-to-submit-your-mcp-server-to-anthropics-connector-directory-from-someone-who-did-it-143m</link>
      <guid>https://dev.to/qrflows/how-to-submit-your-mcp-server-to-anthropics-connector-directory-from-someone-who-did-it-143m</guid>
      <description>&lt;p&gt;A practical guide based on real submission experience — what the form asks, what reviewers check, and what I got wrong the first time.&lt;/p&gt;




&lt;p&gt;I built an MCP server for &lt;a href="https://qrflows.app" rel="noopener noreferrer"&gt;QRflows&lt;/a&gt; — a dynamic QR code platform. After the server was live and working, I submitted it to Anthropic's official Connector Directory. The review is ongoing, but the process taught me things I couldn't find documented anywhere in one place.&lt;/p&gt;

&lt;p&gt;This post covers the full submission process as it stands in mid-2026: what the form actually asks, the technical requirements that can silently kill your review, and how to prepare so you don't spend a week backtracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why bother with the directory at all?
&lt;/h2&gt;

&lt;p&gt;When your MCP server is not in the directory, users have to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Claude Settings → Connectors&lt;/li&gt;
&lt;li&gt;Click "Add custom connector"&lt;/li&gt;
&lt;li&gt;Paste your server URL manually&lt;/li&gt;
&lt;li&gt;Authenticate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's four steps with a copy-paste. Most non-developer users won't do it.&lt;/p&gt;

&lt;p&gt;Once your server is in the directory, users just find you in the list, click Connect, and authorize. Same OAuth flow — but the friction disappears. The directory is also how Anthropic surfaces integrations to Claude Pro and Team users who never look at developer docs.&lt;/p&gt;

&lt;p&gt;For a SaaS product, that's a significant distribution channel.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you can submit
&lt;/h2&gt;

&lt;p&gt;The directory accepts three types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Remote MCP server&lt;/strong&gt; — an internet-hosted server with tools, resources, prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desktop extension&lt;/strong&gt; — local MCP server packaged as an &lt;code&gt;.mcpb&lt;/code&gt; bundle for Claude Desktop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP App&lt;/strong&gt; — an MCP server that renders interactive UI inside the chat (needs extra screenshots)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;QRflows is a remote MCP server. The form and required assets differ by type, so confirm which one you're submitting before you start.&lt;/p&gt;




&lt;h2&gt;
  
  
  The submission form — what it actually asks
&lt;/h2&gt;

&lt;p&gt;The form is at &lt;code&gt;claude.com/docs/connectors/building/submission&lt;/code&gt;. It's not short.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0e52cwoavymqmhhhr3a8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0e52cwoavymqmhhhr3a8.png" alt="Six-section overview of the Anthropic MCP Directory submission form" width="772" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what each section covers so you can prepare everything before you open it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Server basics&lt;/strong&gt; — name, tagline, server URL, connector type (remote / desktop / MCP App), primary use cases in 2–3 sentences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Connection details&lt;/strong&gt; — transport protocol (must be Streamable HTTP, SSE is no longer accepted), auth type, read/write capabilities, whether your OAuth callback URL is registered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Tools &amp;amp; resources&lt;/strong&gt; — list every tool with a human-readable title, confirm annotations are in place, confirm read and write tools are separate, confirm tool names are ≤ 64 chars.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Documentation&lt;/strong&gt; — public docs URL (a single help page or blog post is enough), minimum 3 example prompts that exercise different tools, setup and auth steps. You can share a private staging link with Anthropic during review if docs aren't public yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Privacy &amp;amp; compliance&lt;/strong&gt; — privacy policy URL, data handling summary (what you collect, how long you keep it, who you share it with), confirmation of HTTPS and Origin-header validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Test account &amp;amp; branding&lt;/strong&gt; — login credentials for a test account with realistic sample data, server logo (URL or SVG), favicon. For MCP Apps only: 3–5 PNG screenshots at min 1000px wide.&lt;/p&gt;




&lt;h2&gt;
  
  
  The technical requirements that trip people up
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Tool annotations — the #1 reason for rejection
&lt;/h3&gt;

&lt;p&gt;Every tool needs two things: a &lt;code&gt;title&lt;/code&gt; and the right safety hint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28mhf1lhnbqdaalme7j0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28mhf1lhnbqdaalme7j0.png" alt="Tool annotation examples showing readOnlyHint and destructiveHint for list, create, and delete QR tools" width="800" height="271"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Read-only tool&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list_qr_codes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;List all QR codes in the account.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;List QR Codes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destructiveHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Write tool&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_qr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create a new dynamic QR code.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create QR Code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destructiveHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Destructive tool — be explicit&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete_qr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Permanently delete a QR code.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Delete QR Code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destructiveHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Missing annotations reportedly cause around 30% of all directory rejections. The other mistake: don't bundle read and write into one generic tool. A tool called &lt;code&gt;api_request&lt;/code&gt; with a &lt;code&gt;method&lt;/code&gt; parameter accepting &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; will fail review. Split them.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. OAuth callback URL
&lt;/h3&gt;

&lt;p&gt;For the browser-based Claude (claude.ai), register exactly this redirect URI:&lt;br&gt;
&lt;a href="https://claude.ai/api/mcp/auth_callback" rel="noopener noreferrer"&gt;https://claude.ai/api/mcp/auth_callback&lt;/a&gt;&lt;br&gt;
Claude Code uses a loopback redirect on &lt;code&gt;localhost&lt;/code&gt; with an ephemeral port. If you want to support Claude Code users, your auth server needs to accept loopback redirects with the port ignored.&lt;/p&gt;

&lt;p&gt;PKCE with &lt;code&gt;S256&lt;/code&gt; is required. Your authorization server metadata should declare:&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;"code_challenge_methods_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"S256"&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;Plain OAuth 2.0 without PKCE won't pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Origin-header validation
&lt;/h3&gt;

&lt;p&gt;Your MCP server must validate the &lt;code&gt;Origin&lt;/code&gt; header and reject requests not coming from Claude. This is a security requirement, not optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cloudflare Workers example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="s2"&gt;Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://claude.ai&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Forbidden&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&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;h3&gt;
  
  
  4. Protected Resource Metadata
&lt;/h3&gt;

&lt;p&gt;Claude uses this to discover your authorization server. Host it at &lt;code&gt;/.well-known/oauth-protected-resource&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;"resource"&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://mcp.qrflows.app"&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_servers"&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;"https://qrflows.app"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scopes_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"qr:read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qr:write"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this endpoint is missing, Claude can't complete OAuth discovery and the connector won't authenticate.&lt;/p&gt;




&lt;h2&gt;
  
  
  The documentation requirement
&lt;/h2&gt;

&lt;p&gt;Anthropic wants docs that let a reviewer test your connector in 10 minutes without prior knowledge of your product.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;What the connector does (2–3 sentences)&lt;/li&gt;
&lt;li&gt;How to connect step by step&lt;/li&gt;
&lt;li&gt;At least 3 example prompts that exercise different tools&lt;/li&gt;
&lt;li&gt;What the expected output looks like&lt;/li&gt;
&lt;li&gt;Privacy policy link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For QRflows I created &lt;a href="https://qrflows.app/mcp" rel="noopener noreferrer"&gt;qrflows.app/mcp&lt;/a&gt; as the dedicated docs page. A single well-organized page is enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The test account
&lt;/h2&gt;

&lt;p&gt;Create a separate account — not your main production account. Load it with realistic sample data. Write down the credentials before you open the form.&lt;/p&gt;

&lt;p&gt;For QRflows I created a test account with 6–7 QR codes of different types (URL, WiFi, vCard, Smart Rules) so reviewers could test &lt;code&gt;list_qr_codes&lt;/code&gt;, &lt;code&gt;get_qr_stats&lt;/code&gt;, and &lt;code&gt;update_qr_url&lt;/code&gt; with real data.&lt;/p&gt;

&lt;p&gt;If your tools operate on empty state, reviewers can't tell if they work or are broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  Review timeline
&lt;/h2&gt;

&lt;p&gt;Anthropic is upfront: they can't promise to review or respond to every submission individually because of volume. The typical timeline they cite is ~2 weeks, but it can run longer.&lt;/p&gt;

&lt;p&gt;My submission has been in review for about a month. No response yet — from what I can tell that's within the normal range given submission volume right now.&lt;/p&gt;

&lt;p&gt;While you're waiting: your server is already usable as a custom connector. Share the URL, write about it, put it in your docs. The directory listing is a distribution multiplier, not a prerequisite.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Read the submission guide before building.&lt;/strong&gt; Once I had the full list of requirements, I realized I was missing the Protected Resource Metadata endpoint and had to add it after the fact. Twenty minutes of reading upfront would have saved two hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load the test account before submitting.&lt;/strong&gt; I initially created a test account with no data. Reviewers testing &lt;code&gt;list_qr_codes&lt;/code&gt; would have seen an empty array with no way to tell if the tool worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write docs first.&lt;/strong&gt; The form asks for a public docs URL. If you don't have it ready, you have to pause. Write the docs page before you open the form.&lt;/p&gt;




&lt;h2&gt;
  
  
  Submission checklist
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfl2xe0z9d0gks7uw0mf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfl2xe0z9d0gks7uw0mf.png" alt="Pre-submission checklist covering server, OAuth, tools, docs, test account, and branding" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;QRflows MCP server: &lt;code&gt;https://mcp.qrflows.app/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;QRflows MCP docs: &lt;a href="https://qrflows.app/mcp" rel="noopener noreferrer"&gt;qrflows.app/mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Submission guide: &lt;code&gt;claude.com/docs/connectors/building/submission&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP spec: &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;modelcontextprotocol.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;My previous post: &lt;a href="https://dev.to/qrflows/how-i-built-an-mcp-server-so-claude-can-create-qr-codes-from-chat-205j"&gt;How I Built an MCP Server So Claude Can Create QR Codes From Chat&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I'll update this post when the review completes — approved or not. Happy to answer questions in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>claude</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built an MCP Server So Claude Can Create QR Codes From Chat</title>
      <dc:creator>QRflows</dc:creator>
      <pubDate>Fri, 29 May 2026 21:11:00 +0000</pubDate>
      <link>https://dev.to/qrflows/how-i-built-an-mcp-server-so-claude-can-create-qr-codes-from-chat-205j</link>
      <guid>https://dev.to/qrflows/how-i-built-an-mcp-server-so-claude-can-create-qr-codes-from-chat-205j</guid>
      <description>&lt;p&gt;I launched a SaaS product called &lt;a href="https://qrflows.app" rel="noopener noreferrer"&gt;QRflows&lt;/a&gt; — a dynamic QR code platform. Two months in, I decided to build an MCP (Model Context Protocol) server for it. Now Claude can create, update, and track QR codes directly from a chat conversation, without touching a dashboard.&lt;/p&gt;

&lt;p&gt;This post is about why I built it, how it works technically, and what I learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is MCP and why did I care
&lt;/h2&gt;

&lt;p&gt;MCP is Anthropic's open protocol that lets AI assistants like Claude connect to external services. Think of it like a USB standard — any MCP-compatible server can plug into Claude and give it new tools.&lt;/p&gt;

&lt;p&gt;For QRflows, this meant Claude could become a QR code manager. A user types "create a QR code for my restaurant menu that routes to the breakfast version before 11am and the dinner version after" — and it just happens.&lt;/p&gt;

&lt;p&gt;That's a real use case for my product's Smart Rules feature. Before MCP, the user had to go to the dashboard, create a QR, set up routing rules manually. With MCP, the whole thing is one sentence in chat.&lt;/p&gt;

&lt;p&gt;Here's what it looks like in practice — Claude fetching stats across all QR codes for a full month:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bksp07xh9ajo52m3vx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bksp07xh9ajo52m3vx4.png" alt="Claude fetching April stats for all 20 QR codes" width="683" height="784"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;QRflows itself runs on Laravel + React. But the MCP server is completely separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: Cloudflare Workers (deployed with &lt;code&gt;wrangler deploy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP SDK&lt;/strong&gt;: &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt;: OAuth 2.0 (required by Anthropic for remote MCP servers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: Cloudflare KV for OAuth token store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server lives at &lt;code&gt;mcp.qrflows.app&lt;/code&gt; and communicates via HTTP (Streamable HTTP transport).&lt;/p&gt;




&lt;h2&gt;
  
  
  The tools I implemented
&lt;/h2&gt;

&lt;p&gt;The MCP server exposes 10 tools to Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create_qr          — create a new QR code (16 types supported)
update_qr_url      — change the destination URL without reprinting
update_qr          — update any QR fields
update_wifi_qr     — update WiFi credentials
list_qr_codes      — list all QR codes in the account
get_qr             — get details for a specific QR
get_qr_stats       — get scan analytics
delete_qr          — delete a QR code
apply_smart_rules  — set geo/device/time routing rules
get_account_usage  — check plan limits and usage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each tool has proper MCP annotations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_qr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create a new dynamic QR code in QRflows (16 QR types).&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Human-readable name for the QR code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;qr_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qrTypes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Payload by type, e.g. url: { url }; smart_rules: { default_url, rules: [...] }&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create QR Code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destructiveHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;idempotentHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;openWorldHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&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;QrflowsApi&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;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;toolSuccessWithUserMarkdownLeadingJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createQr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;destructiveHint&lt;/code&gt;, and &lt;code&gt;idempotentHint&lt;/code&gt; annotations are important — they tell Claude how to handle each tool safely. Read-only tools like &lt;code&gt;list_qr_codes&lt;/code&gt; get &lt;code&gt;readOnlyHint: true&lt;/code&gt;. The &lt;code&gt;delete_qr&lt;/code&gt; tool gets &lt;code&gt;destructiveHint: true&lt;/code&gt; so Claude knows to be careful.&lt;/p&gt;




&lt;h2&gt;
  
  
  OAuth was the hard part
&lt;/h2&gt;

&lt;p&gt;Anthropic requires remote MCP servers to implement OAuth 2.0. This makes sense — you don't want Claude connecting to a QR service without the user explicitly authorizing it.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User adds QRflows as a connector in Claude&lt;/li&gt;
&lt;li&gt;Claude redirects to &lt;code&gt;mcp.qrflows.app/auth/authorize&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User logs in with their QRflows credentials&lt;/li&gt;
&lt;li&gt;OAuth token is issued and stored in Cloudflare KV&lt;/li&gt;
&lt;li&gt;All subsequent MCP tool calls use that token to hit the QRflows API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trickiest part was the token refresh logic. Cloudflare Workers have no persistent state between requests, so I use KV with TTL to store tokens and refresh them automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Store in KV with expiry&lt;/span&gt;
&lt;span class="k"&gt;await&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;OAUTH_STORE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`token:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&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="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="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expires_at&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expirationTtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&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;h2&gt;
  
  
  Smart Rules through chat
&lt;/h2&gt;

&lt;p&gt;The most interesting use case is Smart Rules — QRflows' geo/device/time routing feature.&lt;/p&gt;

&lt;p&gt;A user can tell Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a QR code that sends people in Spain to qrflows.app/es and everyone else to qrflows.app"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude translates this into a &lt;code&gt;create_qr&lt;/code&gt; call with &lt;code&gt;qr_type: "smart_rules"&lt;/code&gt; and the right routing config:&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;"qr_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smart_rules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&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;"default_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://qrflows.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"condition_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"condition_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"destination_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://qrflows.app/es"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Spain"&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;This is where natural language + structured API really clicks. Describing routing rules in JSON is annoying. Describing them in English is natural. Claude handles the translation.&lt;/p&gt;

&lt;p&gt;And when you ask for stats on a specific QR, Claude returns a full analytics breakdown inline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0elligra6py5bfen6q4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0elligra6py5bfen6q4r.png" alt="Claude showing scan stats for a specific QR code with chart" width="678" height="606"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Response format: markdown first
&lt;/h2&gt;

&lt;p&gt;One thing I got wrong initially: my tools returned raw JSON. Claude would display it as a code block and the conversation felt clunky.&lt;/p&gt;

&lt;p&gt;The fix was to make tools return a &lt;strong&gt;markdown block first&lt;/strong&gt;, then the JSON for Claude's internal use, separated by a delimiter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Here's your QR code for the restaurant menu:

&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;QR Code&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://qrflows.app/qr/abc123.png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="gs"&gt;**Download:**&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;PNG&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; | &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;SVG&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gs"&gt;**Destination:**&lt;/span&gt; https://your-menu.com
&lt;span class="gs"&gt;**Type:**&lt;/span&gt; Menu QR
&lt;span class="p"&gt;
---&lt;/span&gt;
qrflows_tool_json
{ ... raw json ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude surfaces the markdown naturally in conversation. The JSON after the delimiter is parsed by Claude if it needs to chain tool calls.&lt;/p&gt;

&lt;p&gt;When listing all QR codes, Claude also renders a full breakdown table automatically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vivpxlts73kyffpi710.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vivpxlts73kyffpi710.png" alt="Claude rendering a full table of all QR codes with scan counts" width="692" height="880"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to connect it right now
&lt;/h2&gt;

&lt;p&gt;The MCP server is live at &lt;code&gt;https://mcp.qrflows.app/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To connect in Claude (claude.ai):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Connectors&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add custom connector&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enter &lt;code&gt;https://mcp.qrflows.app/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authorize with your QRflows account&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a WiFi QR code for my office. SSID: OfficeWifi, password: correct-horse-battery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me scan stats for all my QR codes from last week
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update the URL on my "Menu QR" to point to https://myrestaurant.com/menu-v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Submitting to Anthropic's connector directory
&lt;/h2&gt;

&lt;p&gt;I've submitted QRflows to Anthropic's official MCP connector directory. The review is ongoing — once approved, QRflows will appear in the built-in connector list inside Claude without users needing to add a custom URL.&lt;/p&gt;

&lt;p&gt;If you're building an MCP server and wondering about the submission process: there's a Google Form linked from the Anthropic docs. The technical requirements include Streamable HTTP transport, OAuth 2.0, and proper tool annotations. The review takes a few weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with OAuth earlier.&lt;/strong&gt; I built all 10 tools first, then retrofitted OAuth. That was backwards. OAuth shapes your entire architecture — do it first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test with real Claude conversations, not just the MCP inspector.&lt;/strong&gt; The inspector tells you if tools work. Only real conversations tell you if Claude uses them correctly. Claude sometimes misinterprets tool descriptions in ways the inspector won't catch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep tool descriptions conversational.&lt;/strong&gt; I initially wrote terse, developer-style descriptions. Claude is better at using tools described the way you'd explain them to a colleague.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;QRflows: &lt;a href="https://qrflows.app" rel="noopener noreferrer"&gt;qrflows.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP server endpoint: &lt;code&gt;https://mcp.qrflows.app/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP docs: &lt;a href="https://qrflows.app/mcp" rel="noopener noreferrer"&gt;qrflows.app/mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Developer API: &lt;a href="https://qrflows.app/developer-api" rel="noopener noreferrer"&gt;qrflows.app/developer-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP spec: &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;modelcontextprotocol.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built this in about 2 weeks alongside the main product. Happy to answer questions about the implementation — drop them in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>claude</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From idea to indexed: how I launched a SaaS in 60 days with Laravel + React</title>
      <dc:creator>QRflows</dc:creator>
      <pubDate>Sat, 23 May 2026 16:16:52 +0000</pubDate>
      <link>https://dev.to/qrflows/from-idea-to-indexed-how-i-launched-a-saas-in-60-days-with-laravel-react-3gje</link>
      <guid>https://dev.to/qrflows/from-idea-to-indexed-how-i-launched-a-saas-in-60-days-with-laravel-react-3gje</guid>
      <description>&lt;p&gt;60 days from first commit to Google indexing. Here's what I built, &lt;br&gt;
what stack I chose, and what actually slowed me down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;QRflows is a dynamic QR code platform. The core idea: QR codes you &lt;br&gt;
can edit after printing, with real-time scan analytics, Smart Rules &lt;br&gt;
routing, and landing pages — all in one dashboard.&lt;/p&gt;

&lt;p&gt;Sounds simple. The execution was not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wswt4zmarx5ssydaw82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wswt4zmarx5ssydaw82.png" alt=" " width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Laravel + React
&lt;/h2&gt;

&lt;p&gt;I needed something that could scale without becoming a mess at &lt;br&gt;
50k users, but also ship fast enough that I'd still be alive by &lt;br&gt;
launch day.&lt;/p&gt;

&lt;p&gt;Laravel handles the backend — API, auth, queue jobs, database. &lt;br&gt;
React handles the frontend — the dashboard, QR builder, analytics &lt;br&gt;
views. They communicate through a clean REST API.&lt;/p&gt;

&lt;p&gt;This separation matters. When the frontend needs to change, the &lt;br&gt;
backend doesn't care. When the backend logic gets complex, the &lt;br&gt;
frontend stays simple. Scalability isn't just about servers — it's &lt;br&gt;
about not painting yourself into a corner architecturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest part
&lt;/h2&gt;

&lt;p&gt;Getting the UX and architecture to work together without &lt;br&gt;
compromising either one.&lt;/p&gt;

&lt;p&gt;It's easy to build something scalable that's painful to use. It's &lt;br&gt;
easy to build something beautiful that falls apart under load. &lt;br&gt;
Getting both right at the same time — that's where the 60 days went.&lt;/p&gt;

&lt;p&gt;Specific example: the QR builder. It needed to feel instant and &lt;br&gt;
intuitive for non-technical users, but under the hood it had to &lt;br&gt;
handle 16 different QR types, each with different validation rules, &lt;br&gt;
different output formats, and different downstream jobs.&lt;/p&gt;

&lt;p&gt;The solution was a clean abstraction layer in Laravel — each QR type &lt;br&gt;
is its own class with a shared interface — combined with a React &lt;br&gt;
component that adapts its form fields dynamically based on the &lt;br&gt;
selected type. No spaghetti. No special cases everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyaqqrvrmxncxilyw7v8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyaqqrvrmxncxilyw7v8r.png" alt=" " width="800" height="738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics that actually tell you something
&lt;/h2&gt;

&lt;p&gt;Most QR tools show you a scan count. That's it. &lt;/p&gt;

&lt;p&gt;I wanted analytics that suggest what to do next — not just what &lt;br&gt;
happened. So the dashboard shows engagement score, scan consistency, &lt;br&gt;
peak hours, and actionable recommendations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmu1fhborxdk1q86mtho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmu1fhborxdk1q86mtho.png" alt=" " width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One feature I didn't plan but users needed
&lt;/h2&gt;

&lt;p&gt;A/B testing for QR codes. One printed code, multiple destinations, &lt;br&gt;
weighted traffic split. &lt;/p&gt;

&lt;p&gt;Turned out marketing teams using physical materials needed this badly &lt;br&gt;
— they couldn't run experiments without reprinting everything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuh1jx6m8hjxvzrbx3k66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuh1jx6m8hjxvzrbx3k66.png" alt=" " width="800" height="860"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;Start with fewer QR types. I launched with 16. I should have launched &lt;br&gt;
with 5 and added the rest based on what users actually needed.&lt;/p&gt;

&lt;p&gt;Premature completeness is just as dangerous as premature optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it is now
&lt;/h2&gt;

&lt;p&gt;QRflows is live at &lt;a href="https://qrflows.app" rel="noopener noreferrer"&gt;qrflows.app&lt;/a&gt;. Free trial, &lt;br&gt;
no credit card. Still early — but indexed, shipping, and getting &lt;br&gt;
first users.&lt;/p&gt;

&lt;p&gt;If you're building something similar with Laravel + React I'm happy &lt;br&gt;
to compare notes. What's your biggest architectural headache right now?&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>react</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
