<?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: Waleed Siddiqui</title>
    <description>The latest articles on DEV Community by Waleed Siddiqui (@waleed_siddiqui_2c02a3819).</description>
    <link>https://dev.to/waleed_siddiqui_2c02a3819</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%2F3997432%2Ff99fc0e9-1527-487c-aac2-b792e7ad2eb8.png</url>
      <title>DEV Community: Waleed Siddiqui</title>
      <link>https://dev.to/waleed_siddiqui_2c02a3819</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/waleed_siddiqui_2c02a3819"/>
    <language>en</language>
    <item>
      <title>How Much Does a SaaS MVP Cost? What Moves the Number</title>
      <dc:creator>Waleed Siddiqui</dc:creator>
      <pubDate>Mon, 02 Nov 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/waleed_siddiqui_2c02a3819/how-much-does-a-saas-mvp-cost-what-moves-the-number-1ef</link>
      <guid>https://dev.to/waleed_siddiqui_2c02a3819/how-much-does-a-saas-mvp-cost-what-moves-the-number-1ef</guid>
      <description>&lt;p&gt;Founders ask "how much will this cost?" before almost anything else, and most answers online are either a vague "it depends" or a range so wide it is useless. Here is ours, and — more usefully — what moves a project from one end of it to the other.&lt;/p&gt;

&lt;p&gt;Our SaaS MVP package starts at &lt;strong&gt;$38,500&lt;/strong&gt; and typically takes &lt;strong&gt;8 to 12 weeks&lt;/strong&gt; from signed scope to launch. A narrower validation build can cost less; a product with iOS and Android apps, payments and several integrations costs more. Those numbers are for a production-ready first version on a codebase you keep building on, not a clickable prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MVP budget actually pays for
&lt;/h2&gt;

&lt;p&gt;An MVP is a test: you want to learn, quickly and cheaply, whether people will use and pay for the thing. That means the budget should buy three things and nothing else:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One core workflow, done well&lt;/strong&gt; — the single job your first users hire the product to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real signals&lt;/strong&gt; — real sign-up, real payment if revenue is part of the test, and analytics on the actions that matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A foundation you won't throw away&lt;/strong&gt; — authentication, data model, payments and deployment built properly, so version two extends the MVP instead of replacing it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else — admin dashboards with charts, five integrations, a native app "because competitors have one" — is a candidate to wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seven drivers that move the number
&lt;/h2&gt;

&lt;p&gt;Screens are rarely what makes an MVP expensive. Rules are. These are the drivers we look at when we estimate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Driver&lt;/th&gt;
&lt;th&gt;Cheaper end&lt;/th&gt;
&lt;th&gt;Expensive end&lt;/th&gt;
&lt;th&gt;Why it costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User roles&lt;/td&gt;
&lt;td&gt;One type of user&lt;/td&gt;
&lt;td&gt;Buyers and sellers, admins, team members with different rights&lt;/td&gt;
&lt;td&gt;Every role multiplies screens, permission rules and test cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing model&lt;/td&gt;
&lt;td&gt;One plan via Stripe Checkout&lt;/td&gt;
&lt;td&gt;Tiers, trials, seat or usage pricing, upgrades and downgrades&lt;/td&gt;
&lt;td&gt;Subscription edge cases (failed payments, proration, scheduled downgrades) need careful webhook handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integrations&lt;/td&gt;
&lt;td&gt;None, or one well-documented API&lt;/td&gt;
&lt;td&gt;CRM, calendar, accounting, SSO, legacy systems&lt;/td&gt;
&lt;td&gt;Each integration brings auth, rate limits, retries and two-way sync decisions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platforms&lt;/td&gt;
&lt;td&gt;Responsive web app&lt;/td&gt;
&lt;td&gt;Web plus iOS and Android&lt;/td&gt;
&lt;td&gt;Mobile adds store review, in-app purchase rules and release management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI features&lt;/td&gt;
&lt;td&gt;None, or one well-scoped feature&lt;/td&gt;
&lt;td&gt;AI is the core value&lt;/td&gt;
&lt;td&gt;You need an evaluation set, cost controls and fallbacks, not just an API call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance and data sensitivity&lt;/td&gt;
&lt;td&gt;Standard SaaS data&lt;/td&gt;
&lt;td&gt;Health, financial or children’s data&lt;/td&gt;
&lt;td&gt;Audit logging, access controls, data residency and vendor agreements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data migration&lt;/td&gt;
&lt;td&gt;Starting fresh&lt;/td&gt;
&lt;td&gt;Moving users and payments off an existing tool&lt;/td&gt;
&lt;td&gt;Mapping, reconciliation and a cut-over plan for live customers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of these come from projects in &lt;a href="https://www.dablewdevs.com/work" rel="noopener noreferrer"&gt;our portfolio&lt;/a&gt;. A &lt;a href="https://www.dablewdevs.com/work#membership-platform-stripe-billing" rel="noopener noreferrer"&gt;membership platform we built&lt;/a&gt; needed tiered Stripe subscriptions, scheduled downgrades, payment-failure grace periods and a production data migration off a no-code tool — the billing and migration rows accounted for a large share of the engineering. Our &lt;a href="https://www.dablewdevs.com/work#privacy-first-mobile-marketplace" rel="noopener noreferrer"&gt;own mobile product&lt;/a&gt; needed iOS and Android, in-app subscriptions and strict privacy rules enforced in the database. Neither was expensive because of its screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we never cut, even in an MVP
&lt;/h2&gt;

&lt;p&gt;Cutting scope is how you control an MVP budget. Cutting these is how you end up paying twice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and access rules.&lt;/strong&gt; Retrofitting security after users have data is expensive and risky. Use a proven auth provider and enforce access on the server — ideally in the database itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real payments, if revenue is part of the test.&lt;/strong&gt; "We'll invoice manually" works for five customers; build Stripe (web) or RevenueCat (in-app purchases) properly when payment is the signal you need. Treat the payment provider's webhooks as the source of truth, and make them idempotent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data model.&lt;/strong&gt; It is the hardest decision to change later. A day spent here saves weeks after launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment you own.&lt;/strong&gt; Production infrastructure in your accounts, with CI/CD and error tracking, so the MVP is a product rather than a demo on someone's laptop.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to genuinely reduce the cost
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Remove features, not quality.&lt;/strong&gt; Ask of every feature: would a first user refuse to pay without it? If not, it waits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do things manually behind the scenes.&lt;/strong&gt; Onboarding, reporting and even some matching can be done by a person at first. Automate once you know the process is worth automating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buy the commodity parts.&lt;/strong&gt; Authentication, billing, email delivery and file storage are solved problems. Managed services cost less than building them, especially at MVP scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with web.&lt;/strong&gt; A responsive web app tests most B2B ideas. Add native apps when usage shows people need them — React Native lets you reuse much of the logic when that day comes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defer the admin panel.&lt;/strong&gt; Your database console and a few saved queries are an acceptable admin tool for the first months.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The costs that arrive after launch
&lt;/h2&gt;

&lt;p&gt;Build cost is a one-off. These are recurring, and they are the ones founders most often leave out of their plan. Prices below are as published on each provider's pricing page in October 2026 — check them yourself before budgeting, because they change.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Typical starting point&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hosting (frontend / serverless)&lt;/td&gt;
&lt;td&gt;Vercel Pro: $20 per month per developer seat&lt;/td&gt;
&lt;td&gt;Vercel’s free Hobby plan is for personal, non-commercial use only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database and auth&lt;/td&gt;
&lt;td&gt;Supabase Pro: from $25 per month&lt;/td&gt;
&lt;td&gt;Free tier exists, but free projects pause after a week of inactivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment processing&lt;/td&gt;
&lt;td&gt;Stripe: 2.9% + 30¢ per successful US domestic card transaction&lt;/td&gt;
&lt;td&gt;Stripe Billing (subscriptions) adds 0.7% of billing volume on pay-as-you-go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email, monitoring, analytics&lt;/td&gt;
&lt;td&gt;Often free at MVP volume&lt;/td&gt;
&lt;td&gt;Becomes a real line item as you grow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI model usage&lt;/td&gt;
&lt;td&gt;Per token, per provider&lt;/td&gt;
&lt;td&gt;Measure cost per user action during the build so pricing covers it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Ongoing engineering time&lt;/td&gt;
&lt;td&gt;Dependency updates, security patches, bug fixes and small improvements&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Plan for maintenance from day one&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software that nobody touches decays: dependencies go out of date, APIs you rely on change, app stores raise their requirements. Budget for a small amount of engineering every month after launch, even if no new features are planned.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fixed price or time and materials?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Fixed price&lt;/th&gt;
&lt;th&gt;Time and materials&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best when&lt;/td&gt;
&lt;td&gt;Scope is clear and written down&lt;/td&gt;
&lt;td&gt;Scope is genuinely uncertain, or you expect to change direction often&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your risk&lt;/td&gt;
&lt;td&gt;Lower on budget; changes go through a change request&lt;/td&gt;
&lt;td&gt;Higher on budget; lower on flexibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What makes it work&lt;/td&gt;
&lt;td&gt;A scoping workshop and a written scope both sides sign&lt;/td&gt;
&lt;td&gt;Short cycles, weekly demos and a clear priority list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For MVPs we prefer a fixed price for an agreed scope: it forces the scoping conversation that keeps an MVP small, and it gives a founder a number they can plan around. Work after launch usually moves to a monthly retainer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read the quotes you receive
&lt;/h2&gt;

&lt;p&gt;If you collect several quotes, they will often differ by a factor of three or more. That spread usually reflects different assumptions rather than different hourly rates. Before comparing totals, compare what each quote actually includes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is the scope written down feature by feature?&lt;/td&gt;
&lt;td&gt;A one-line description ("SaaS platform with subscriptions") lets each vendor imagine a different product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are billing edge cases mentioned?&lt;/td&gt;
&lt;td&gt;Failed payments, upgrades, downgrades and cancellations are where subscription work hides&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who owns the code, repositories and cloud accounts?&lt;/td&gt;
&lt;td&gt;You should — from the first commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is deployment, monitoring and a staging environment included?&lt;/td&gt;
&lt;td&gt;Without them you get a demo, not a product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is testing included, and on what?&lt;/td&gt;
&lt;td&gt;At minimum: sign-up, payment and the core workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What happens after launch?&lt;/td&gt;
&lt;td&gt;Bug-fix period, maintenance options and how changes are priced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A quote far below the others is often missing several of these. A quote far above may be pricing in a larger team, more process, or a scope the vendor inflated to protect themselves. Ask each vendor what they would cut to halve the price — the answer tells you how well they understood your product.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Red flag&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any estimate given before anyone has asked about your user roles, how you will charge, or which systems the product must connect to. Those three questions decide most of the cost.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting an estimate you can trust
&lt;/h2&gt;

&lt;p&gt;A credible estimate needs more than a feature list. Bring the problem you are solving, who the first users are, how you plan to charge, which systems it must connect to, and your deadline. Be wary of any quote produced without questions about roles, billing or data — those are where the real cost hides.&lt;/p&gt;

&lt;p&gt;If you would like us to do this with you, our &lt;a href="https://www.dablewdevs.com/industry/startups" rel="noopener noreferrer"&gt;MVP development service&lt;/a&gt; starts with a scoping workshop and ends with a fixed estimate. For products that are past the MVP stage, see &lt;a href="https://www.dablewdevs.com/services/saas-development" rel="noopener noreferrer"&gt;SaaS development&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How much does a SaaS MVP cost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our SaaS MVP package starts at $38,500. Narrower validation builds can cost less; products with mobile apps, payments and several integrations cost more. We give a fixed estimate after a scoping workshop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does it take to build a SaaS MVP?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Typically 6 to 12 weeks from signed scope to launch for a focused MVP. Adding native mobile apps, complex billing or heavy AI features extends that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I build an MVP with no-code tools instead?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For testing demand, sometimes yes. The trade-off appears later: when the product needs complex billing, permissions or integrations, teams often have to migrate — and a production data migration is real work. We have done one off a no-code platform, and we price it as its own line item.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What ongoing costs should I budget after launch?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hosting, database, payment processing fees, email and monitoring services, AI usage if relevant, and a small monthly amount of engineering time for maintenance and security updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stripe.com/pricing" rel="noopener noreferrer"&gt;Stripe — Pricing (standard US card processing; Stripe Billing), accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/pricing" rel="noopener noreferrer"&gt;Vercel — Pricing (Pro plan; Hobby plan for non-commercial use), accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.com/pricing" rel="noopener noreferrer"&gt;Supabase — Pricing (Free and Pro plans), accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.dablewdevs.com/blog/saas-mvp-cost" rel="noopener noreferrer"&gt;DablewDevs blog&lt;/a&gt;. DablewDevs builds production AI systems and software — AI agents, RAG, MCP servers and SaaS products. &lt;a href="https://www.dablewdevs.com/pricing" rel="noopener noreferrer"&gt;See what we build and what it costs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>saas</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Build a Production MCP Server for Your SaaS</title>
      <dc:creator>Waleed Siddiqui</dc:creator>
      <pubDate>Fri, 30 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/waleed_siddiqui_2c02a3819/how-to-build-a-production-mcp-server-for-your-saas-29dp</link>
      <guid>https://dev.to/waleed_siddiqui_2c02a3819/how-to-build-a-production-mcp-server-for-your-saas-29dp</guid>
      <description>&lt;p&gt;Most MCP tutorials end with a local script that exposes two tools and reads an API key from an environment variable. That is a fine way to learn the protocol. It is not something you can give to customers, or to forty staff who each have different permissions.&lt;/p&gt;

&lt;p&gt;We recently built a remote MCP server for a client's operations platform — a leadership-development consultancy that runs invoicing, scheduling, contracts, HR and time tracking on a custom admin app we had already built for them. Staff now connect Claude to it in one click and run routine work by asking. The server exposes roughly 115 tools. This article covers the decisions that mattered, in the order you will hit them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote or local: decide before you write a tool
&lt;/h2&gt;

&lt;p&gt;MCP defines two standard transports. &lt;strong&gt;stdio&lt;/strong&gt; runs the server as a local process the client launches; &lt;strong&gt;Streamable HTTP&lt;/strong&gt; runs it as a web endpoint the client connects to by URL. The protocol messages are the same; almost everything around them is different.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Local (stdio)&lt;/th&gt;
&lt;th&gt;Remote (Streamable HTTP)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup for users&lt;/td&gt;
&lt;td&gt;Install a package, edit a config file&lt;/td&gt;
&lt;td&gt;Paste a URL, sign in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credentials&lt;/td&gt;
&lt;td&gt;Read from the environment (often a shared API key)&lt;/td&gt;
&lt;td&gt;OAuth — each connection acts as one user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who it suits&lt;/td&gt;
&lt;td&gt;Developers, single-user tools&lt;/td&gt;
&lt;td&gt;Customers, non-technical staff, teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Updates&lt;/td&gt;
&lt;td&gt;Every user upgrades their install&lt;/td&gt;
&lt;td&gt;You deploy once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth spec applies?&lt;/td&gt;
&lt;td&gt;No — the spec says stdio servers should take credentials from the environment&lt;/td&gt;
&lt;td&gt;Yes — the MCP authorization spec is written for HTTP transports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our first version for the client was a local stdio server. It worked, and nobody outside engineering used it. Moving to a hosted endpoint — no install, no config file, sign in with the account you already have — is what turned it from a demo into something staff use. If your users are not developers, start remote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication: the server acts as a person, not as "the AI"
&lt;/h2&gt;

&lt;p&gt;For HTTP transports, the MCP authorization spec builds on OAuth 2.1. Your MCP server plays the role of an OAuth &lt;strong&gt;resource server&lt;/strong&gt;: it accepts access tokens and serves requests. A separate (or co-hosted) &lt;strong&gt;authorization server&lt;/strong&gt; signs the user in and issues those tokens. The pieces you need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protected Resource Metadata (RFC 9728).&lt;/strong&gt; The MCP server must publish metadata telling clients which authorization server to use. An unauthenticated request gets a 401 whose WWW-Authenticate header points at that metadata, and the client takes it from there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client registration.&lt;/strong&gt; Clients need a client ID before starting the flow. The current spec prefers Client ID Metadata Documents, allows pre-registration, and keeps Dynamic Client Registration (RFC 7591) for backwards compatibility. Check which mechanisms the clients your users run actually support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PKCE and the resource parameter.&lt;/strong&gt; Clients use PKCE and must send a resource parameter naming your server, so the token is issued for your server specifically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audience validation.&lt;/strong&gt; Your server must reject tokens that were not issued for it. Do not accept, and never pass through, tokens meant for some other API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scopes.&lt;/strong&gt; A 403 with error="insufficient_scope" and the required scopes lets clients step up permissions only when needed, instead of asking for everything up front.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you already have an identity provider, you usually do not need to build an authorization server from scratch — but you do need to check it supports what MCP clients expect. In our build, sign-in reuses the platform's existing accounts, so revoking someone's access to the app also revokes their assistant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The rule that matters most&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every tool call runs with the identity and permissions of the person who connected the client. There is no service account with god-mode access sitting behind the assistant. When something goes wrong, the audit log names a human.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Make tools thin proxies over the API you already trust
&lt;/h2&gt;

&lt;p&gt;The fastest way to create a security hole is to give the MCP server its own database access and re-implement business rules inside tool handlers. Now you have two copies of "who can send an invoice" and they will drift.&lt;/p&gt;

&lt;p&gt;Every one of our ~115 tools calls an existing, authenticated endpoint of the platform — the same validation the web app relies on. The MCP layer translates between "what the model asked for" and "what the API accepts", and nothing else. Two consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model cannot do anything the API would reject for that user. Prompt injection can at worst trigger an action the user was already allowed to take — which is why confirmations still matter (below).&lt;/li&gt;
&lt;li&gt;The server stays small. Adding a tool is mostly writing a description and an input schema, then mapping to an endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your product has no clean API yet, build that first. It is the part of the work that pays off regardless of MCP — see our &lt;a href="https://www.dablewdevs.com/services/api-development" rel="noopener noreferrer"&gt;API integration services&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What registering a tool looks like
&lt;/h2&gt;

&lt;p&gt;Here is a minimal, generic example using the official TypeScript SDK (the v2 @modelcontextprotocol/server package). It builds a server per request for the signed-in user, registers one read-only tool, and serves it over Streamable HTTP from a web-standard route handler such as a Next.js App Router route. Check the SDK documentation for your installed version — the SDK moved from @modelcontextprotocol/sdk (v1) to separate server and client packages in v2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMcpHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;McpServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod/v4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verifyAccessToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// your token + audience check&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                  &lt;span class="c1"&gt;// your existing, validated API client&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&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;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acme-operations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list_overdue_invoices&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;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;List invoices in the user&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;s workspace that are past their due date. Read-only. &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Use before drafting reminder emails.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;olderThanDays&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;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&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="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&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="mi"&gt;30&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;olderThanDays&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="c1"&gt;// Same endpoint the web app uses, called as this user.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listOverdue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;olderThanDays&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;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;invoices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&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;verifyAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 401 with WWW-Authenticate if missing/invalid&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;user&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&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;createMcpHandler&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;buildServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)).&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;req&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;Building the server per request keeps it stateless and makes per-user tool sets trivial: a read-only user simply never gets the write tools registered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool design is prompt design
&lt;/h2&gt;

&lt;p&gt;With a handful of tools, models pick the right one almost every time. With a hundred, tool descriptions and schemas become the main thing standing between "it works" and "it picks the wrong tool and apologises". What helped us most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Design around jobs, not endpoints.&lt;/strong&gt; Users ask "chase the overdue invoices", not "GET /invoices?status=overdue". Name and describe tools in the language of the task. Sometimes that means one tool combining two endpoints, or splitting one endpoint into two clearer tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent verb_noun names.&lt;/strong&gt; list_, get_, create_, update_, send_ across every domain. The model learns the pattern; so do your engineers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say what the tool is not for.&lt;/strong&gt; "Read-only", "does not send the email — use send_email after the user confirms" and similar lines prevent a surprising number of mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tight input schemas.&lt;/strong&gt; Enums instead of free strings, bounded numbers, explicit date formats. Every constraint the schema enforces is one the model cannot get wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compact outputs.&lt;/strong&gt; Return the fields the model needs to answer and act, not the full database row. Large outputs cost tokens and bury the useful part.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Confirmations, audit logs and limits
&lt;/h2&gt;

&lt;p&gt;Permissions decide what an assistant &lt;strong&gt;may&lt;/strong&gt; do. They do not decide what it &lt;strong&gt;should&lt;/strong&gt; do on a particular request. For anything irreversible or external — sending an email, issuing an invoice, deleting a record — we draft first and ask the user to confirm in the conversation before the write tool runs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit every call&lt;/strong&gt; with user, tool, arguments (minus secrets) and result status. You will need it the first time someone asks "did the assistant send that?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-limit per user&lt;/strong&gt;, separately from your normal API limits — agents can loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat tool results as untrusted input to the model.&lt;/strong&gt; Content from emails, documents or customer notes can contain instructions. Keep destructive actions behind confirmation precisely because of this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test with the clients your users actually run
&lt;/h2&gt;

&lt;p&gt;Unit tests on tool handlers are necessary and nowhere near sufficient. The failure modes that matter show up only when a real model chooses among your real tools. We kept a list of realistic tasks ("reschedule Thursday's session and tell the participant", "which invoices from last month are unpaid?") and ran them through Claude after every batch of tool changes, watching which tools it chose and where it hesitated. Most fixes were edits to descriptions, not code.&lt;/p&gt;

&lt;p&gt;Also test the sign-in flow end to end in each client you support. Client support for the authorization features differs, and it moves quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: put it next to the API
&lt;/h2&gt;

&lt;p&gt;We hosted the MCP server inside the existing Next.js application as one more route, so it ships with the product, uses the same configuration and secrets management, and cannot fall out of step with the API it proxies. A separate service is reasonable when your backend is not HTTP-friendly or you need independent scaling — but co-location removes a whole class of version-skew bugs.&lt;/p&gt;

&lt;p&gt;Plan for maintenance. The MCP specification and SDKs are still evolving, and clients adopt new versions at different speeds. Budget a little time each quarter to track spec changes and re-test with your supported clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist before you launch
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Ready when…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;Remote endpoint over Streamable HTTP; users add it by URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Protected Resource Metadata published; tokens audience-checked; revocation works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permissions&lt;/td&gt;
&lt;td&gt;Each call runs as the connected user; tool set follows their role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;Thin proxies over validated endpoints; job-oriented names; tight schemas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety&lt;/td&gt;
&lt;td&gt;Destructive actions need confirmation; per-user rate limits; full audit log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Realistic task list run in every supported client after each change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operations&lt;/td&gt;
&lt;td&gt;Monitoring and error alerts; owner for spec and SDK upgrades&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the full &lt;a href="https://www.dablewdevs.com/work#mcp-server-operations-platform" rel="noopener noreferrer"&gt;case study&lt;/a&gt;, or see how we approach &lt;a href="https://www.dablewdevs.com/services/mcp-server-development" rel="noopener noreferrer"&gt;MCP server development&lt;/a&gt; for SaaS products and internal platforms.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How long does it take to build an MCP server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A read-only server over a clean existing API, with OAuth and a handful of tools, is a matter of weeks. Time grows with the number of tools, write actions that need confirmation flows, and how much API groundwork is missing. Testing tool selection with real clients is a real part of the schedule, not an afterthought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we need our own OAuth authorization server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not necessarily. MCP servers act as OAuth resource servers; the authorization server can be your existing identity provider if it supports what MCP clients need (discovery metadata, PKCE, a client registration mechanism the clients support). If it does not, you can host a small authorization server alongside the MCP server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should every API endpoint become a tool?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Map the jobs users want to delegate and design tools around those. Large, endpoint-shaped tool sets make models worse at choosing the right tool and are harder to secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is an MCP server safe to expose to customers?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can be, if every call runs as the authenticated user, tools go through your validated API, destructive actions require confirmation, and calls are rate-limited and logged. The risk is the same as any API that acts on a user's behalf — plus prompt injection from untrusted content, which is why confirmations matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization" rel="noopener noreferrer"&gt;Model Context Protocol — Authorization specification (2026-07-28)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;MCP TypeScript SDK (GitHub)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ts.sdk.modelcontextprotocol.io/v2/serving/http.html" rel="noopener noreferrer"&gt;MCP TypeScript SDK v2 docs — serving over HTTP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc9728" rel="noopener noreferrer"&gt;RFC 9728 — OAuth 2.0 Protected Resource Metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8707.html" rel="noopener noreferrer"&gt;RFC 8707 — Resource Indicators for OAuth 2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.dablewdevs.com/blog/build-mcp-server-for-saas" rel="noopener noreferrer"&gt;DablewDevs blog&lt;/a&gt;. DablewDevs builds production AI systems and software — AI agents, RAG, MCP servers and SaaS products. &lt;a href="https://www.dablewdevs.com/pricing" rel="noopener noreferrer"&gt;See what we build and what it costs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>saas</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How to Add AI Features to an Existing SaaS (No Rewrite)</title>
      <dc:creator>Waleed Siddiqui</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/waleed_siddiqui_2c02a3819/how-to-add-ai-features-to-an-existing-saas-no-rewrite-4fm6</link>
      <guid>https://dev.to/waleed_siddiqui_2c02a3819/how-to-add-ai-features-to-an-existing-saas-no-rewrite-4fm6</guid>
      <description>&lt;p&gt;Your customers are asking for AI features, and your codebase was not designed with them in mind. The good news: you almost never need a rewrite or a separate "AI platform". An LLM feature is a feature like any other — it lives next to the code it serves, goes through your review process and ships behind a flag.&lt;/p&gt;

&lt;p&gt;What makes AI features different is that their output is probabilistic, their cost scales with usage, and they send data to a third party. This is the playbook we use to handle those three facts when we add AI to existing products as part of our &lt;a href="https://www.dablewdevs.com/services/ai-integration" rel="noopener noreferrer"&gt;AI integration work&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: pick the first feature with two questions
&lt;/h2&gt;

&lt;p&gt;Score candidate features on two axes: &lt;strong&gt;how much value a correct output creates&lt;/strong&gt;, and &lt;strong&gt;whether the data the model needs is already in your system and accessible&lt;/strong&gt;. The first feature should score high on both.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature type&lt;/th&gt;
&lt;th&gt;Typical value&lt;/th&gt;
&lt;th&gt;Data usually available?&lt;/th&gt;
&lt;th&gt;Good first feature?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extracting fields from documents or emails into records&lt;/td&gt;
&lt;td&gt;High — removes manual data entry&lt;/td&gt;
&lt;td&gt;Yes, it is the input&lt;/td&gt;
&lt;td&gt;Often the best first choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Summaries and generated reports from existing data&lt;/td&gt;
&lt;td&gt;High for busy users&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes, if users can see and correct the result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Classification and routing (tickets, leads, transactions)&lt;/td&gt;
&lt;td&gt;Medium–high&lt;/td&gt;
&lt;td&gt;Yes, plus historical labels&lt;/td&gt;
&lt;td&gt;Yes — and easy to measure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic search over the product’s content&lt;/td&gt;
&lt;td&gt;Medium–high&lt;/td&gt;
&lt;td&gt;Yes, needs indexing&lt;/td&gt;
&lt;td&gt;Yes, but plan for retrieval work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An assistant that takes actions in the app&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Yes, via your API&lt;/td&gt;
&lt;td&gt;Better as a second feature, once the scaffolding exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A general chatbot on the marketing site&lt;/td&gt;
&lt;td&gt;Low–medium&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Rarely the best place to start&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Users can see and correct the result" matters more than it looks. A feature where a person reviews the AI output before it is used is far more forgiving than one that acts on its own — and it generates the labelled examples you will use to evaluate and improve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: four architecture defaults
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A provider-agnostic model layer
&lt;/h3&gt;

&lt;p&gt;Put model calls behind one small interface in your codebase, with the model chosen by configuration. Providers, prices and model quality change every few months; switching should be a config change plus an evaluation run, not a refactor. SDKs such as the Vercel AI SDK or LangChain already give you this abstraction across Anthropic, OpenAI and Google models.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Structured output, validated against a schema
&lt;/h3&gt;

&lt;p&gt;If software consumes the result, never parse free text. Ask for output that matches a schema and validate it. In the Vercel AI SDK this is &lt;strong&gt;generateText&lt;/strong&gt; with an &lt;strong&gt;Output.object&lt;/strong&gt; schema; the parsed, validated value comes back as &lt;strong&gt;output&lt;/strong&gt;, and a response that cannot be parsed into the schema throws a &lt;strong&gt;NoObjectGeneratedError&lt;/strong&gt; you can handle explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NoObjectGeneratedError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Invoice&lt;/span&gt; &lt;span class="o"&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;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;vendor&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="na"&gt;invoiceNumber&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="na"&gt;dueDate&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="s1"&gt;ISO 8601 date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;total&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;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;currency&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;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&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;extractInvoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;output&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// e.g. from config, so the provider is swappable&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Invoice&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Extract the invoice fields from this document:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NoObjectGeneratedError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Route to a human review queue instead of saving bad data.&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unparseable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&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;We used the same principle in an &lt;a href="https://www.dablewdevs.com/work#ai-leadership-assessment-platform" rel="noopener noreferrer"&gt;AI assessment app we built&lt;/a&gt;: assessment templates are generated as schema-constrained JSON, so the app never has to guess whether the model "probably" returned valid structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Streaming where users wait, background jobs everywhere else
&lt;/h3&gt;

&lt;p&gt;Interactive features (an assistant, a draft the user watches being written) should stream. Everything else — extraction, classification, report generation — belongs in a background job with retries, so model latency and provider outages never block a web request. Your existing queue is usually fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Ship behind a feature flag
&lt;/h3&gt;

&lt;p&gt;Roll out to internal users, then a few friendly customers, then everyone. Capture a thumbs-up/down or an edit signal on every output from day one; it is the cheapest evaluation data you will ever get.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: get multi-tenancy right before launch
&lt;/h2&gt;

&lt;p&gt;Most AI tutorials assume one user and one dataset. A SaaS has many tenants, and the AI layer must respect the same boundaries as the rest of the product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation in retrieval.&lt;/strong&gt; If the feature searches customer content, filter by tenant inside the search query itself — never retrieve broadly and filter afterwards. Storing embeddings in PostgreSQL with pgvector keeps vectors next to your existing tenant and permission columns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No cross-tenant prompts.&lt;/strong&gt; Prompt templates, few-shot examples and caches must never include another customer's data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-tenant usage metering.&lt;/strong&gt; Record tokens, model and feature for every call, tagged with tenant and user. You need it for pricing, for abuse limits and to answer "why did our bill double?".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-tenant limits and kill switches.&lt;/strong&gt; One customer's bulk import should not exhaust your rate limits or budget for everyone else.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Price the feature with real numbers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Measure cost per user action during the pilot. Only then decide whether the feature is included in existing plans, metered, or sold as an add-on — guessing here is how AI features quietly erode margins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: know exactly what data goes where
&lt;/h2&gt;

&lt;p&gt;Your customers' security questionnaires will ask which data reaches which AI provider. Answer it on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send the minimum.&lt;/strong&gt; Redact or drop fields the model does not need. It improves accuracy and cost as well as privacy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use paid API tiers and read their data terms.&lt;/strong&gt; As of October 2026: OpenAI states that data sent to its API is not used to train its models unless you opt in, and that abuse-monitoring logs are kept for up to 30 days, with zero data retention available to approved customers. Anthropic states it does not use inputs or outputs from its commercial products for training by default. Google's Gemini API terms say prompts and responses from paid services are not used to improve its products, while unpaid-tier data may be used and reviewed by people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document it.&lt;/strong&gt; Keep a short data-flow note per AI feature: fields sent, provider, retention, region. Update your subprocessor list and privacy policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: put evaluation in CI
&lt;/h2&gt;

&lt;p&gt;Every prompt edit or model upgrade can make the feature better on the example you were looking at and worse on ten you weren't. Treat AI output like any other behaviour under test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collect 30–100 real inputs with the output a domain expert considers correct.&lt;/li&gt;
&lt;li&gt;Score automatically: exact checks for structured fields, and an LLM-as-judge rubric for free text — but only after checking the judge's verdicts against human labels, so you know when it can be trusted.&lt;/li&gt;
&lt;li&gt;Run the suite in CI on every change to prompts, models or retrieval, and block merges that regress.&lt;/li&gt;
&lt;li&gt;Add every real-world failure your users report to the suite.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Mistakes we see when teams add AI themselves
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Starting with the most impressive feature instead of the most valuable one.&lt;/strong&gt; A general assistant demos well; extraction or classification usually pays back first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Putting raw tables in the prompt.&lt;/strong&gt; Models do better with less, well-labelled context — the relevant records, not the whole table. It is cheaper too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusting a single happy-path test.&lt;/strong&gt; A prompt that works on the example you wrote it against tells you very little. An evaluation set of real inputs tells you a lot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard-coding one provider everywhere.&lt;/strong&gt; When prices drop or a better model ships, you want to switch in an afternoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting the failure path.&lt;/strong&gt; Timeouts, rate limits and refusals happen in production. Decide what the user sees when they do, and make sure the rest of the product keeps working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No owner after launch.&lt;/strong&gt; Model behaviour drifts as providers update models and your data changes. Someone needs to watch the feedback signal and the evaluation results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A rollout checklist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before launch&lt;/th&gt;
&lt;th&gt;Done when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model behind an interface&lt;/td&gt;
&lt;td&gt;Changing provider is a config change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema-validated outputs&lt;/td&gt;
&lt;td&gt;Invalid output is handled, never stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evaluation suite in CI&lt;/td&gt;
&lt;td&gt;Merges that regress are blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenant isolation&lt;/td&gt;
&lt;td&gt;Retrieval and prompts are tenant-scoped and tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage metering and limits&lt;/td&gt;
&lt;td&gt;Cost per tenant and per feature is visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallback behaviour&lt;/td&gt;
&lt;td&gt;The product still works when the provider is down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data-flow documentation&lt;/td&gt;
&lt;td&gt;Security questionnaires can be answered from it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature flag and feedback capture&lt;/td&gt;
&lt;td&gt;You can roll back instantly and see user corrections&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of this requires a new platform or a rewrite. It does require engineers who treat AI as production software. That is what we do in our &lt;a href="https://www.dablewdevs.com/services/ai-integration" rel="noopener noreferrer"&gt;AI integration service&lt;/a&gt; — inside your codebase, following your conventions — and if you would rather have an engineer join your team directly, you can &lt;a href="https://www.dablewdevs.com/hire-developers/ai-developers" rel="noopener noreferrer"&gt;hire AI developers&lt;/a&gt; from us. For the SaaS foundations around it, see &lt;a href="https://www.dablewdevs.com/services/saas-development" rel="noopener noreferrer"&gt;SaaS development&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Do we need to rebuild our product to add AI?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Almost never. AI features can be added inside your existing codebase and infrastructure, behind a feature flag, using the same review and deployment process as any other feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which AI feature should we build first?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One where a correct output creates clear value and the data the model needs is already in your system — structured extraction, summaries, classification or semantic search are common first choices. Features where users review the output before it is used are the safest start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will our customers’ data be used to train AI models?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not on the paid API tiers of the major providers by default, according to their published terms as of October 2026 — but terms differ (for example, Google treats unpaid Gemini API usage differently). We document exactly which data reaches which provider for each feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we control AI costs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Meter tokens per tenant and per feature, choose the smallest model that passes your evaluation set, cache where possible, set per-tenant limits, and price the feature from measured cost per action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data" rel="noopener noreferrer"&gt;AI SDK — Generating structured data (generateText with Output.object), accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/guides/your-data" rel="noopener noreferrer"&gt;OpenAI — Data controls in the OpenAI platform, accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training" rel="noopener noreferrer"&gt;Anthropic Privacy Center — Is my data used for model training?, accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/terms" rel="noopener noreferrer"&gt;Google — Gemini API Additional Terms of Service, accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector — README&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.dablewdevs.com/blog/add-ai-to-existing-saas" rel="noopener noreferrer"&gt;DablewDevs blog&lt;/a&gt;. DablewDevs builds production AI systems and software — AI agents, RAG, MCP servers and SaaS products. &lt;a href="https://www.dablewdevs.com/pricing" rel="noopener noreferrer"&gt;See what we build and what it costs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>saas</category>
      <category>webdev</category>
      <category>llm</category>
    </item>
    <item>
      <title>How Much Does It Cost to Build an AI Agent?</title>
      <dc:creator>Waleed Siddiqui</dc:creator>
      <pubDate>Sat, 24 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/waleed_siddiqui_2c02a3819/how-much-does-it-cost-to-build-an-ai-agent-27lj</link>
      <guid>https://dev.to/waleed_siddiqui_2c02a3819/how-much-does-it-cost-to-build-an-ai-agent-27lj</guid>
      <description>&lt;p&gt;Ask ten agencies what an AI agent costs and you will get ten numbers, most of them meaningless without the scope behind them. "An agent" can mean a tool that drafts replies for a person to send, or a system that issues invoices and moves records between three products without anyone watching.&lt;/p&gt;

&lt;p&gt;So instead of a price list, this article explains what actually drives the two bills every agent has — &lt;strong&gt;the cost to build it&lt;/strong&gt; and &lt;strong&gt;the cost to run it&lt;/strong&gt; — with worked running-cost arithmetic at current API list prices. It is based on the agent and AI-integration work we do for clients and the production agent systems our founder builds, and it deliberately avoids quoting numbers we cannot back up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two bills: building the agent and running it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build cost&lt;/strong&gt; is engineering time: scoping, connecting the agent to your systems, making it safe, testing it, and rolling it out. It is mostly a one-off, front-loaded cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running cost&lt;/strong&gt; is model usage (tokens), plus the infrastructure, monitoring and upkeep that keep the agent accurate as your data, workflows and models change. It scales with usage and never quite goes to zero.&lt;/p&gt;

&lt;p&gt;Teams routinely over-focus on token prices, which for most business agents are the smaller of the two. Model calls for an internal workflow agent often cost less per month than a single day of engineering. The expensive part is making the agent trustworthy enough to leave running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What drives the build cost
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Driver&lt;/th&gt;
&lt;th&gt;Cheaper end&lt;/th&gt;
&lt;th&gt;Expensive end&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Systems the agent touches&lt;/td&gt;
&lt;td&gt;One system with a clean API&lt;/td&gt;
&lt;td&gt;Several systems, some without APIs&lt;/td&gt;
&lt;td&gt;Every integration needs tools, auth, error handling and tests; missing APIs must be built first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reversibility of actions&lt;/td&gt;
&lt;td&gt;Read-only or drafts for a person&lt;/td&gt;
&lt;td&gt;Sends, pays, deletes, files externally&lt;/td&gt;
&lt;td&gt;Irreversible actions need approval steps, idempotency and audit trails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data quality&lt;/td&gt;
&lt;td&gt;Structured records, consistent formats&lt;/td&gt;
&lt;td&gt;Scanned PDFs, free text, conflicting sources&lt;/td&gt;
&lt;td&gt;Messy inputs need extraction, validation and fallbacks before the agent can reason over them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accuracy bar&lt;/td&gt;
&lt;td&gt;"Good enough to draft"&lt;/td&gt;
&lt;td&gt;"Right every time it acts alone"&lt;/td&gt;
&lt;td&gt;A higher bar needs a bigger evaluation set and more iterations to meet it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume and latency&lt;/td&gt;
&lt;td&gt;Batch jobs, minutes are fine&lt;/td&gt;
&lt;td&gt;User-facing, seconds matter&lt;/td&gt;
&lt;td&gt;Low latency pushes you to smaller models, caching, streaming and parallel tool calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security and compliance&lt;/td&gt;
&lt;td&gt;Internal data, no regulation&lt;/td&gt;
&lt;td&gt;Personal, financial or health data&lt;/td&gt;
&lt;td&gt;Data handling, retention, provider agreements and access controls take real design time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When we scope an agent, these six questions decide most of the estimate. A read-only internal assistant over one well-structured system sits at one end; an autonomous agent that takes irreversible actions across several systems with regulated data sits at the other. Both are "AI agents".&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the build effort goes, phase by phase
&lt;/h2&gt;

&lt;p&gt;These phases hold for almost every agent we have built or reviewed. Durations depend heavily on the drivers above, so treat the ranges as orders of magnitude, not quotes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scoping and the evaluation set (typically about a week or two).&lt;/strong&gt; Pick one workflow, write down what "done correctly" means, and collect real examples — often 30 to 100. This is the cheapest phase and the one that most often saves money, because it can show the idea will not work before anything is built.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototype against real data (often a few weeks).&lt;/strong&gt; A thin agent on your actual systems in a sandbox, scored against the evaluation set. You learn whether the model, the data or the tools are the bottleneck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production hardening (frequently the largest phase).&lt;/strong&gt; Permissions inherited from real users, approval steps, retries, idempotent actions, durable state for long-running work, tracing, cost tracking and an interface people actually use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradual rollout (weeks, overlapping with operation).&lt;/strong&gt; Shadow mode first (the agent proposes, a person acts), then supervised autonomy, then autonomy only on the cases the evaluation shows it handles reliably.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The common budgeting mistake&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Budgeting for the prototype and calling it the project. A prototype that works on good examples is often well under half of the work; hardening and rollout are where agents earn the trust to run unattended. Our &lt;a href="https://www.dablewdevs.com/blog/ai-poc-to-production" rel="noopener noreferrer"&gt;AI proof of concept to production&lt;/a&gt; article covers that gap in detail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Running cost: worked token arithmetic at current prices
&lt;/h2&gt;

&lt;p&gt;Model usage is billed per token, separately for input (what you send: instructions, tool definitions, retrieved context, conversation so far) and output (what the model writes). Agents are input-heavy, because every step re-sends the growing context.&lt;/p&gt;

&lt;p&gt;Take a modest workflow agent: &lt;strong&gt;8 steps per run&lt;/strong&gt;, adding up to &lt;strong&gt;40,000 input tokens and 3,000 output tokens per run&lt;/strong&gt; in total. Here is the cost per run and per 1,000 runs at list prices published on the providers' pricing pages on 20 October 2026 (standard tier, short prompts, before any caching or batch discounts):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model (list price per 1M tokens, in / out)&lt;/th&gt;
&lt;th&gt;Input cost per run&lt;/th&gt;
&lt;th&gt;Output cost per run&lt;/th&gt;
&lt;th&gt;Per run&lt;/th&gt;
&lt;th&gt;Per 1,000 runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5.5 ($2 / $10)&lt;/td&gt;
&lt;td&gt;40,000 × $2 ÷ 1M = $0.080&lt;/td&gt;
&lt;td&gt;3,000 × $10 ÷ 1M = $0.030&lt;/td&gt;
&lt;td&gt;$0.110&lt;/td&gt;
&lt;td&gt;$110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI gpt-6.1-sol ($2 / $10)&lt;/td&gt;
&lt;td&gt;$0.080&lt;/td&gt;
&lt;td&gt;$0.030&lt;/td&gt;
&lt;td&gt;$0.110&lt;/td&gt;
&lt;td&gt;$110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.6 Flash ($0.75 / $3.75, through 31 Dec 2026)&lt;/td&gt;
&lt;td&gt;40,000 × $0.75 ÷ 1M = $0.030&lt;/td&gt;
&lt;td&gt;3,000 × $3.75 ÷ 1M = $0.011&lt;/td&gt;
&lt;td&gt;$0.041&lt;/td&gt;
&lt;td&gt;$41.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 5.5 ($0.10 / $0.50, prompts ≤ 100K)&lt;/td&gt;
&lt;td&gt;40,000 × $0.10 ÷ 1M = $0.004&lt;/td&gt;
&lt;td&gt;3,000 × $0.50 ÷ 1M = $0.0015&lt;/td&gt;
&lt;td&gt;$0.0055&lt;/td&gt;
&lt;td&gt;$5.50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 1,000 runs a month, the token bill ranges from a few dollars to a little over a hundred depending on the model. Two things move it far more than the per-token price:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt caching.&lt;/strong&gt; Much of an agent's input — system instructions and tool definitions — repeats on every step. Providers bill cached input at a fraction of the normal rate (for example $0.10 instead of $2 per million on Claude Sonnet 5.5 and gpt-6.1-sol). If 30,000 of the 40,000 input tokens were cache reads, the Sonnet run above would fall from about $0.11 to about $0.053 — roughly half. Cache writes can be billed differently; check the current pricing page for your provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choosing the smallest model that passes your evaluation set.&lt;/strong&gt; Routing easy steps (classification, extraction) to a small model and reserving a larger one for genuine reasoning often cuts cost by an order of magnitude without hurting accuracy. The evaluation set is what makes that decision safe rather than a guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prices change often and differ by region, tier and context length. Re-run this arithmetic with current prices and your own token counts from a prototype — we measure tokens per run during the prototype phase so the operating cost is known before rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The running costs nobody budgets for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation upkeep.&lt;/strong&gt; New failure cases get added to the test set and every prompt, tool or model change is re-scored. This is cheap per run and essential; skipping it is how agents quietly get worse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring and tracing.&lt;/strong&gt; Storing traces of every step and tool call so you can answer "why did it do that?" Tools such as Langfuse or an OpenTelemetry pipeline cost money or hosting time, and someone has to look at them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model upgrades and deprecations.&lt;/strong&gt; Providers retire models. Each migration means re-running the evaluation set, adjusting prompts and sometimes tools. Keeping the model behind an interface makes this a routine task instead of a rewrite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration drift.&lt;/strong&gt; The APIs your agent calls change too. An agent is a piece of software with dependencies, not a configuration you set once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human review time.&lt;/strong&gt; In supervised mode, people review the agent's proposals. That time is a real cost — and the main thing a good agent reduces over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When you should not build a custom agent
&lt;/h2&gt;

&lt;p&gt;Custom agents are worth it when the workflow is specific to your business, touches systems you control, and runs often enough to matter. They are poor value when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An off-the-shelf product already does the job inside a tool you use (many help desks, CRMs and document tools now ship decent built-in assistants).&lt;/li&gt;
&lt;li&gt;The process runs a few times a month — a checklist and a person will be cheaper for years.&lt;/li&gt;
&lt;li&gt;Nobody can define what a correct result looks like. If you cannot build an evaluation set, you cannot know whether the agent works.&lt;/li&gt;
&lt;li&gt;The real problem is missing data or a missing API. Fix that first; it pays off with or without AI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We say this in scoping calls too: sometimes the right recommendation is a small integration or a better form, not an agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to get an estimate you can trust
&lt;/h2&gt;

&lt;p&gt;Any agency that quotes a firm price for "an AI agent" before seeing your workflow, systems and data is guessing. A useful estimate comes from a short scoping exercise that produces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A written description of one workflow and what "correct" means for it.&lt;/li&gt;
&lt;li&gt;An evaluation set of real cases, even a small one.&lt;/li&gt;
&lt;li&gt;A list of systems the agent must read from and write to, with the state of their APIs.&lt;/li&gt;
&lt;li&gt;A decision on which actions need human approval.&lt;/li&gt;
&lt;li&gt;Measured tokens per run from a thin prototype, turned into a monthly running-cost figure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is exactly how we start &lt;a href="https://www.dablewdevs.com/services/ai-agent-development" rel="noopener noreferrer"&gt;AI agent development&lt;/a&gt; projects. If you already have an agent prototype and want it production-ready, see &lt;a href="https://www.dablewdevs.com/services/ai-integration" rel="noopener noreferrer"&gt;AI integration services&lt;/a&gt; — or &lt;a href="https://www.dablewdevs.com/hire-developers/ai-developers" rel="noopener noreferrer"&gt;hire AI developers&lt;/a&gt; to work inside your own team.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How much does it cost to run an AI agent per month?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the example in this article (40,000 input and 3,000 output tokens per run), 1,000 runs cost from about $5.50 to $110 in model fees at October 2026 list prices, depending on the model — less with prompt caching. Add hosting, tracing and evaluation upkeep, which usually matter more than tokens at modest volumes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do AI agent quotes vary so much?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because "AI agent" covers everything from a read-only drafting assistant to an autonomous system that takes irreversible actions across several products. The number of systems touched, whether actions are reversible, data quality, the accuracy bar, latency and compliance requirements drive most of the difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it cheaper to use a bigger model and skip engineering?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rarely. A bigger model can paper over a weak prototype, but it does not add permissions, approvals, idempotency, monitoring or an evaluation set — the things that make an agent safe to leave running. It usually also costs more per run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we start small?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, and we recommend it: one workflow, a real evaluation set, shadow mode first. You learn the true build and running costs on a contained scope before committing to more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://claude.com/pricing" rel="noopener noreferrer"&gt;Claude pricing (Anthropic) — accessed 2026-10-20&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI API pricing — accessed 2026-10-20&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing (Google) — accessed 2026-10-20&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.dablewdevs.com/blog/ai-agent-development-cost" rel="noopener noreferrer"&gt;DablewDevs blog&lt;/a&gt;. DablewDevs builds production AI systems and software — AI agents, RAG, MCP servers and SaaS products. &lt;a href="https://www.dablewdevs.com/pricing" rel="noopener noreferrer"&gt;See what we build and what it costs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>startup</category>
      <category>agents</category>
    </item>
    <item>
      <title>RAG vs Fine-Tuning: A Decision Framework for Business AI</title>
      <dc:creator>Waleed Siddiqui</dc:creator>
      <pubDate>Wed, 21 Oct 2026 09:59:28 +0000</pubDate>
      <link>https://dev.to/waleed_siddiqui_2c02a3819/rag-vs-fine-tuning-a-decision-framework-for-business-ai-gaa</link>
      <guid>https://dev.to/waleed_siddiqui_2c02a3819/rag-vs-fine-tuning-a-decision-framework-for-business-ai-gaa</guid>
      <description>&lt;p&gt;"Should we fine-tune a model on our documents?" is one of the most common questions we hear from teams starting an AI project. Usually the honest answer is no — not because fine-tuning is bad, but because it solves a different problem from the one they have.&lt;/p&gt;

&lt;p&gt;This article is the decision framework we use with clients. It is written from production experience: our founder built and runs a multi-tenant retrieval-augmented generation (RAG) pipeline on an AI EdTech platform used across 20+ US schools, with hybrid semantic and keyword search, per-school data isolation on pgvector, and vision-model ingestion of PDFs. The lessons below come from that system and from the client work we do at &lt;a href="https://www.dablewdevs.com/services/rag-development" rel="noopener noreferrer"&gt;DablewDevs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RAG changes what the model knows at the moment it answers. Fine-tuning changes how the model behaves.&lt;/strong&gt; If your problem is "the model doesn't know our policies, products, contracts or customers", you need retrieval. If your problem is "the model knows enough, but answers in the wrong format, tone or structure, or a cheaper model can't follow the task", fine-tuning is a candidate.&lt;/p&gt;

&lt;p&gt;Most business knowledge projects are the first kind. Your documents change, different users may see different documents, and people need to check where an answer came from. Retrieval handles all three; fine-tuning handles none of them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before either: check whether you need retrieval at all&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anthropic notes that if a knowledge base is under roughly 200,000 tokens (about 500 pages), you can often put the whole thing in the prompt and use prompt caching to keep cost and latency down. For a small, stable document set, that is simpler than building a retrieval pipeline.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Decision table: RAG vs fine-tuning
&lt;/h2&gt;

&lt;p&gt;We score a use case against these questions. If most answers land in the RAG column, start there.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Points to RAG&lt;/th&gt;
&lt;th&gt;Points to fine-tuning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How often does the source information change?&lt;/td&gt;
&lt;td&gt;Weekly, daily or continuously — re-index and the answer changes immediately&lt;/td&gt;
&lt;td&gt;Rarely; the behaviour you want is stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do users need to see where an answer came from?&lt;/td&gt;
&lt;td&gt;Yes — citations to the exact passage&lt;/td&gt;
&lt;td&gt;No, or the output is not a factual answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do different users see different data?&lt;/td&gt;
&lt;td&gt;Yes — permissions are enforced at retrieval time&lt;/td&gt;
&lt;td&gt;No — everything the model learns, every user can extract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is wrong with today’s output?&lt;/td&gt;
&lt;td&gt;Missing or wrong facts&lt;/td&gt;
&lt;td&gt;Right facts, wrong format, tone, structure or classification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How much labelled data do you have?&lt;/td&gt;
&lt;td&gt;Documents are enough to start&lt;/td&gt;
&lt;td&gt;Hundreds to thousands of high-quality input/output examples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you delete information on request?&lt;/td&gt;
&lt;td&gt;Yes — remove it from the index&lt;/td&gt;
&lt;td&gt;Hard — it may be in the weights until you retrain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What are you optimising for at scale?&lt;/td&gt;
&lt;td&gt;Answer accuracy and freshness&lt;/td&gt;
&lt;td&gt;Shorter prompts, lower per-call cost, or a smaller model doing a narrow task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is where fine-tuning genuinely shines. OpenAI's own optimisation guide frames fine-tuning as a way to supply more examples than fit in a prompt, use shorter prompts at scale, and train a smaller, cheaper, faster model to excel at a particular task. Those are behaviour and cost goals — not "teach the model our handbook".&lt;/p&gt;

&lt;h2&gt;
  
  
  The misconception: fine-tuning to teach facts
&lt;/h2&gt;

&lt;p&gt;Fine-tuning on your documents feels like it should produce a model that "knows" them. In practice it is a poor way to load facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No citations.&lt;/strong&gt; The model cannot point to the passage it learned something from, so reviewers have nothing to check against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale by default.&lt;/strong&gt; Every document change means another training run, evaluation and deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No access control.&lt;/strong&gt; Anything in the training data can potentially surface for any user who asks the right question. In a multi-tenant product, that alone rules it out for customer data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confident blending.&lt;/strong&gt; A model that has partially absorbed two versions of a policy will answer fluently from both. Retrieval at least lets you show which version was used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a practical signal in the market: OpenAI's model optimisation guide states that it is winding down its fine-tuning platform and that it is no longer accessible to new users. Fine-tuning remains available elsewhere, but it is not where the major providers are steering knowledge use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to combine them
&lt;/h2&gt;

&lt;p&gt;The two approaches are not exclusive. The pattern we recommend when both are justified is &lt;strong&gt;retrieve the facts, tune the behaviour&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A support assistant retrieves the relevant help-centre articles (RAG) and uses a tuned or heavily prompted model to answer in your house style and escalation format.&lt;/li&gt;
&lt;li&gt;A document-processing pipeline retrieves the relevant contract clauses, then a small tuned classifier labels them against your risk taxonomy at a fraction of a large model's cost.&lt;/li&gt;
&lt;li&gt;A high-volume workflow starts on a large model with retrieval; once you have thousands of reviewed outputs, those become training data for a smaller model doing the same narrow task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice the order. You almost always build retrieval first, because it produces the reviewed examples you would later need for fine-tuning anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where RAG actually fails (it is rarely the model)
&lt;/h2&gt;

&lt;p&gt;Choosing RAG is the easy part. When a RAG system gives a wrong answer, the cause is usually that the right passage was never retrieved. These are the failure modes we see most:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Ingestion destroys structure
&lt;/h3&gt;

&lt;p&gt;PDFs parsed into a flat stream of text lose their headings, tables lose their column headers, and scanned pages come out empty. On the EdTech platform our founder works on, plain text extraction was not good enough for long course documents, so ingestion uses a vision model to read pages, anchored to the document's table of contents, with deterministic settings and content-addressed deduplication so re-ingesting the same file does not create duplicates.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Chunks lose their context
&lt;/h3&gt;

&lt;p&gt;A chunk that says "the fee is waived after 30 days" is useless if it no longer says which product or plan it belongs to. Hierarchical chunking (keeping the section path with each chunk) helps. Anthropic's contextual retrieval technique goes further, prepending a short, model-generated description of where each chunk sits in its document before embedding it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pure vector search misses exact terms
&lt;/h3&gt;

&lt;p&gt;Embeddings are good at meaning and bad at identifiers: part numbers, error codes, course codes, names. Keyword search (BM25 or PostgreSQL full-text) is the opposite. We run both and merge the results with &lt;strong&gt;reciprocal rank fusion (RRF)&lt;/strong&gt;, which scores each document by summing 1 / (k + rank) across the result lists. It needs no training and, in Cormack, Clarke and Büttcher's original SIGIR 2009 paper, outperformed both individual rankers and the Condorcet method. In Anthropic's published experiment, adding contextual BM25 to contextual embeddings cut the top-20 retrieval failure rate by 49%, and adding reranking took the reduction to 67%.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Filters applied too late
&lt;/h3&gt;

&lt;p&gt;If you retrieve the top 20 chunks and then remove the ones the user may not see, you can end up with three results, all irrelevant. Tenant and permission filters belong inside the retrieval query. pgvector makes this natural: vectors live in the same PostgreSQL tables as your permission data, so a WHERE clause enforces isolation in the same query that ranks similarity — which is how per-school isolation works in the system described above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Hybrid retrieval, tenant-filtered inside the query (simplified).&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;semantic&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;keyword&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ts_rank_cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tsv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plainto_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;tsv&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ts_rank_cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tsv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&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="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rrf_score&lt;/span&gt;   &lt;span class="c1"&gt;-- k = 60, as in the RRF paper&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;semantic&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;rrf_score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to measure which approach is working
&lt;/h2&gt;

&lt;p&gt;Whatever you choose, decide how you will know it is right before you build. We measure two things separately, because they fail for different reasons:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;th&gt;How we measure it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval recall&lt;/td&gt;
&lt;td&gt;Did we fetch the passages that contain the answer?&lt;/td&gt;
&lt;td&gt;For each test question, list the source passages a human expects; check whether they appear in the top-k results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answer faithfulness&lt;/td&gt;
&lt;td&gt;Did the answer stay true to what was retrieved?&lt;/td&gt;
&lt;td&gt;A rubric scored by a person, then by an LLM judge whose verdicts are checked against human labels before we trust it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refusal correctness&lt;/td&gt;
&lt;td&gt;Does it say "I don’t know" when nothing relevant was found?&lt;/td&gt;
&lt;td&gt;Include out-of-scope questions in the test set&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start with 50 to 200 real questions from real users, with the documents that should answer them. If retrieval recall is low, no amount of prompt tuning or fine-tuning will fix the answers — improve parsing, chunking or search first. If recall is high but faithfulness is low, then prompt and model changes are worth exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-step way to decide
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write down the failure.&lt;/strong&gt; Missing facts, wrong permissions, or wrong behaviour? Missing facts point to retrieval; wrong behaviour may point to tuning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure the size of the knowledge.&lt;/strong&gt; Under a few hundred pages and stable? Try long-context prompting with caching first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build retrieval with an evaluation set.&lt;/strong&gt; Hybrid search, metadata filters, citations, and the metrics above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the weakest stage.&lt;/strong&gt; Ingestion, chunking, search or prompt — whichever loses the most answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider fine-tuning only for behaviour or cost&lt;/strong&gt;, once you have enough reviewed examples to train on and a measured reason to expect improvement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you already have a RAG prototype whose answers are not good enough to ship, the same framework applies — we start by building the evaluation set and finding where answers are lost. See our &lt;a href="https://www.dablewdevs.com/services/rag-development" rel="noopener noreferrer"&gt;RAG development service&lt;/a&gt;, or, if the knowledge needs to live inside an existing product, our &lt;a href="https://www.dablewdevs.com/services/ai-integration" rel="noopener noreferrer"&gt;AI integration work&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Is RAG cheaper than fine-tuning?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually to start, yes: there is no training run, and updating knowledge means re-indexing documents rather than retraining. At very high volume, a fine-tuned smaller model can be cheaper per call for a narrow task — which is why we sometimes combine the two once a system is proven.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can fine-tuning reduce hallucinations?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can make a model follow your format and refuse out-of-scope requests more consistently, but it does not give the model reliable access to facts. For factual answers, grounding the model in retrieved sources and asking it to cite them is the more dependable approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we need a dedicated vector database for RAG?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not necessarily. If you already run PostgreSQL, pgvector supports HNSW and IVFFlat indexes and works alongside PostgreSQL full-text search for hybrid retrieval, with permissions enforced in the same query. A separate vector database is worth its operational cost only when scale or specific features demand it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does it take to build a production RAG system?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A focused system over one well-structured document set can be built in a few weeks. Messy documents, many sources, complex permissions and high accuracy requirements extend that. We size it after reviewing a sample of the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/guides/model-optimization" rel="noopener noreferrer"&gt;OpenAI — Model optimization guide (fine-tuning use cases; fine-tuning platform wind-down notice), accessed Oct 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.anthropic.com/news/contextual-retrieval" rel="noopener noreferrer"&gt;Anthropic — Introducing Contextual Retrieval&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector — README (HNSW/IVFFlat indexes, hybrid search with full-text search, RRF)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf" rel="noopener noreferrer"&gt;Cormack, Clarke &amp;amp; Büttcher (2009) — Reciprocal Rank Fusion outperforms Condorcet and individual rank learning methods, SIGIR&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.dablewdevs.com/blog/rag-vs-fine-tuning" rel="noopener noreferrer"&gt;DablewDevs blog&lt;/a&gt;. DablewDevs builds production AI systems and software — AI agents, RAG, MCP servers and SaaS products. &lt;a href="https://www.dablewdevs.com/pricing" rel="noopener noreferrer"&gt;See what we build and what it costs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>AI Proof of Concept to Production: What Demos Skip</title>
      <dc:creator>Waleed Siddiqui</dc:creator>
      <pubDate>Wed, 21 Oct 2026 09:57:32 +0000</pubDate>
      <link>https://dev.to/waleed_siddiqui_2c02a3819/ai-proof-of-concept-to-production-what-demos-skip-4hkh</link>
      <guid>https://dev.to/waleed_siddiqui_2c02a3819/ai-proof-of-concept-to-production-what-demos-skip-4hkh</guid>
      <description>&lt;p&gt;A good AI demo takes an afternoon. A model reads a document, produces a summary, and everyone in the room is impressed. Then the project stalls for months, because the demo worked on five hand-picked examples and nobody knows what it does on the five-thousandth real one.&lt;/p&gt;

&lt;p&gt;The gap between proof of concept and production is not mainly about choosing a better model. It is ordinary engineering that demos skip: knowing whether the output is right, making it fit your software, controlling what it can do, knowing what it costs, and rolling it out so a bad day does not reach every customer. This is the checklist we work through when taking AI features to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo vs production at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;In the demo&lt;/th&gt;
&lt;th&gt;In production&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is it right?&lt;/td&gt;
&lt;td&gt;Looks good on a few examples&lt;/td&gt;
&lt;td&gt;Scored against an evaluation set of real cases on every change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output format&lt;/td&gt;
&lt;td&gt;Free text, read by a person&lt;/td&gt;
&lt;td&gt;Schema-constrained output, validated before it touches your data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What can it do?&lt;/td&gt;
&lt;td&gt;Whatever the API key allows&lt;/td&gt;
&lt;td&gt;Acts as a specific user, with that user's permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated actions&lt;/td&gt;
&lt;td&gt;Not considered&lt;/td&gt;
&lt;td&gt;Idempotent writes — retries cannot double-send or double-charge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failures&lt;/td&gt;
&lt;td&gt;Refresh and try again&lt;/td&gt;
&lt;td&gt;Timeouts, retries, rate-limit handling and a non-AI fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;Tokens and cost tracked per feature and per customer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility&lt;/td&gt;
&lt;td&gt;Console logs&lt;/td&gt;
&lt;td&gt;Traces of inputs, outputs, tool calls and latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollout&lt;/td&gt;
&lt;td&gt;Everyone at once&lt;/td&gt;
&lt;td&gt;Shadow mode, feature flag, gradual exposure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Build an evaluation set before you tune anything
&lt;/h2&gt;

&lt;p&gt;The single biggest difference between teams that ship AI features and teams that stall is an evaluation set: a collection of real inputs, with what a correct output looks like, that you can score automatically. Without one, every prompt change is a guess and every model upgrade is a gamble.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use real data, not invented examples.&lt;/strong&gt; Pull cases from production logs, support tickets or documents — including the messy ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a held-out set.&lt;/strong&gt; Tune prompts on one split and report results on another, so you are not grading your own homework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score what matters.&lt;/strong&gt; For classification, accuracy per class. For extraction, field-level correctness. For generated text, a rubric of specific yes/no questions ("does the reply invent facts not in the source?") rather than a vague 1–10 score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add every production failure to the set.&lt;/strong&gt; The evaluation set should grow with the feature.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. If an LLM grades the output, grade the grader
&lt;/h2&gt;

&lt;p&gt;For open-ended output, a second model scoring the first against a rubric ("LLM-as-judge") is often the only scalable option. It is also easy to get wrong: an uncalibrated judge happily passes bad answers or fails good ones, and you end up optimising toward its quirks.&lt;/p&gt;

&lt;p&gt;The method we use, including in our own testing: have people label a sample of outputs against the same rubric, run the judge on that sample, and compare. Track false passes and false fails separately — a judge that passes everything looks great on average. Iterate on the rubric and judge prompt until agreement with human labels is high, and only then trust the judge on the full set. We have also found that a model's self-reported confidence is a poor stand-in for this; measure against labels instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rubrics beat scores&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Five specific yes/no questions written from how a human actually grades the output are far easier to calibrate than a single "quality out of 10".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Make the output fit your software
&lt;/h2&gt;

&lt;p&gt;If code consumes the model's output, "usually valid JSON" is a production incident waiting to happen. Major model APIs support schema-constrained (structured) output: you supply a schema and the response is generated to match it. Validate it anyway before it touches your database.&lt;/p&gt;

&lt;p&gt;In an &lt;a href="https://www.dablewdevs.com/work#ai-leadership-assessment-platform" rel="noopener noreferrer"&gt;AI assessment and reporting app&lt;/a&gt; we built for a leadership consultancy, templates are generated as typed JSON from a free-text topic, and executive summaries follow a fixed section contract. We also gave the summariser labelled findings instead of raw scores, which removed a whole class of confident, numerically wrong statements. Giving the model less — only what it needs — often improves accuracy and lowers cost at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Control what it can do, and make actions safe to repeat
&lt;/h2&gt;

&lt;p&gt;Prototypes typically run with an admin API key. Production AI that takes actions must act as a specific user with that user's permissions, so it can never do more than the person on whose behalf it runs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route actions through your existing validated API&lt;/strong&gt;, not direct database writes, so business rules are enforced in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make writes idempotent.&lt;/strong&gt; Network retries, model retries and duplicate tool calls happen. An invoice must not be sent twice because a request timed out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Require confirmation for irreversible actions&lt;/strong&gt;, especially when the AI reads untrusted content (emails, documents) that could contain injected instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every action&lt;/strong&gt; with the user, inputs and result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the defaults behind the &lt;a href="https://www.dablewdevs.com/work#mcp-server-operations-platform" rel="noopener noreferrer"&gt;remote MCP server&lt;/a&gt; we built so a consultancy's staff could run operations from Claude: each tool proxies an existing authenticated endpoint and inherits the signed-in user's permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Plan for the model being slow, wrong or unavailable
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts and retries with backoff&lt;/strong&gt;, distinguishing per-minute rate limits from daily quotas and from real errors — each needs a different response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Surface retry timing to users&lt;/strong&gt; instead of failing silently when a provider returns a quota error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A non-AI fallback.&lt;/strong&gt; If summarisation fails, show the source; if classification fails, route to a person. The feature degrades instead of breaking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background jobs for slow work.&lt;/strong&gt; Do not hold a web request open for a 30-second model call; queue it and stream or notify.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Know what each request costs
&lt;/h2&gt;

&lt;p&gt;Track tokens and cost per feature, per customer and per model from day one. It turns pricing decisions — included, metered, add-on — into arithmetic, and it catches runaway loops early. It also lets you safely move easy steps to a smaller model: if the evaluation score holds, the saving is real. Our &lt;a href="https://www.dablewdevs.com/blog/ai-agent-development-cost" rel="noopener noreferrer"&gt;AI agent cost breakdown&lt;/a&gt; walks through the token arithmetic at current prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Trace everything
&lt;/h2&gt;

&lt;p&gt;When a customer reports a strange answer, support needs to see exactly what the model received, which tools it called, what came back and how long each step took. LLM-observability tools such as Langfuse, or traces exported through OpenTelemetry, give you that per-request history. Store prompts and outputs only where your data agreements allow, and redact what you do not need.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Roll out gradually
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shadow mode.&lt;/strong&gt; The AI runs on real traffic but only proposes; people still act. Compare its proposals with what they did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature flag to a small group.&lt;/strong&gt; Real users, real feedback, easy rollback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supervised autonomy.&lt;/strong&gt; The AI acts on the cases the evaluation set shows it handles reliably; everything else goes to a person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wider rollout&lt;/strong&gt;, with the evaluation suite running on every prompt or model change and production failures feeding back into it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When the right answer is to stop
&lt;/h2&gt;

&lt;p&gt;Sometimes the evaluation set shows the idea does not work well enough on real data, and no amount of prompt tuning closes the gap. That is a successful outcome of a proof of concept, not a failure — it cost weeks instead of a launch. The honest options are then to narrow the scope, fix the underlying data, keep a person in the loop permanently, or drop the feature.&lt;/p&gt;

&lt;p&gt;If you have a prototype that needs to become a dependable feature, that is the core of our &lt;a href="https://www.dablewdevs.com/services/ai-integration" rel="noopener noreferrer"&gt;AI integration services&lt;/a&gt;. For agents that take actions across systems, see &lt;a href="https://www.dablewdevs.com/services/ai-agent-development" rel="noopener noreferrer"&gt;AI agent development&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Why do so many AI proofs of concept never reach production?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually because nobody can show the feature is reliable on real data. Without an evaluation set, teams cannot tell whether changes help, cannot set an accuracy bar, and cannot justify the risk of launching. Missing permissions, cost tracking and failure handling add to the delay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How big should an evaluation set be?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Large enough to cover the real variety of inputs, including the awkward cases. Many features start with 50 to 200 examples and grow as production failures are added. A small honest set beats a large set of easy, invented examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we trust an LLM to grade AI output?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only after checking it against human labels. Compare the judge with people on a sample, track false passes and false fails separately, and refine the rubric until they agree. Then re-check periodically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we need to rebuild our prototype for production?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not usually. Most of the work is adding what the prototype skipped — evaluation, structured output, permissions, failure handling, tracing and a rollout plan — around the core idea, inside your existing codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/structured-output" rel="noopener noreferrer"&gt;Gemini API — Structured output (Google)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs" rel="noopener noreferrer"&gt;Langfuse documentation — tracing and evaluation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/" rel="noopener noreferrer"&gt;OpenTelemetry documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.dablewdevs.com/blog/ai-poc-to-production" rel="noopener noreferrer"&gt;DablewDevs blog&lt;/a&gt;. DablewDevs builds production AI systems and software — AI agents, RAG, MCP servers and SaaS products. &lt;a href="https://www.dablewdevs.com/pricing" rel="noopener noreferrer"&gt;See what we build and what it costs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>startup</category>
    </item>
    <item>
      <title>Made a free Salah (prayer) app: accurate times, Adhan, Qibla — fully on-device, no data collection</title>
      <dc:creator>Waleed Siddiqui</dc:creator>
      <pubDate>Mon, 22 Jun 2026 18:17:24 +0000</pubDate>
      <link>https://dev.to/waleed_siddiqui_2c02a3819/made-a-free-salah-prayer-app-accurate-times-adhan-qibla-fully-on-device-no-data-collection-22l</link>
      <guid>https://dev.to/waleed_siddiqui_2c02a3819/made-a-free-salah-prayer-app-accurate-times-adhan-qibla-fully-on-device-no-data-collection-22l</guid>
      <description>&lt;p&gt;I'm a solo dev, and as a Muslim I use a prayer-times app every day — but the popular ones got bloated, ad-heavy, and quietly data-hungry (accounts, location uploads, trackers). I wanted the opposite, so I built Salah Tracker.&lt;br&gt;
Everything runs on your device — no account, no login, no servers of mine, no data collection. Your location is only used locally to calculate prayer times and Qibla.&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;br&gt;
What it does:&lt;br&gt;
&amp;nbsp;- Accurate prayer times for your exact location (calculation method + madhab adjustable to match your local mosque)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adhan (call to prayer) notifications, on time, even when the app is closed&lt;/li&gt;
&lt;li&gt;Qibla compass&lt;/li&gt;
&lt;li&gt;Prayer tracking with streaks, plus Azkar &amp;amp; a Tasbih counter&lt;/li&gt;
&lt;li&gt;Light/dark mode&amp;nbsp;
&amp;nbsp;&amp;nbsp;
It's free (one small banner ad) and Android-only for now. It's a v1 from a solo dev, so I'd genuinely love honest feedback on accuracy and the notifications — and what you'd want next.
&amp;nbsp;&amp;nbsp;
Disclosure: I'm the developer. Play Store link in a comment 👇
Play Store: &lt;a href="https://play.google.com/store/apps/details?id=com.istiqamah.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.istiqamah.app&lt;/a&gt;
&amp;nbsp;&amp;nbsp;
Happy to answer anything about how the prayer-time calculations work (methods, madhab, Qibla) — and feedback is very welcome 🙏&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>privacy</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
