<?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: iamTheDev</title>
    <description>The latest articles on DEV Community by iamTheDev (@iamthedev).</description>
    <link>https://dev.to/iamthedev</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%2F4105698%2F8e112c65-3ca4-4d11-a2fd-36125b1ee18d.png</url>
      <title>DEV Community: iamTheDev</title>
      <link>https://dev.to/iamthedev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamthedev"/>
    <language>en</language>
    <item>
      <title>How I Build Small Tools With Claude (and What They Actually Earn)</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Mon, 09 Nov 2026 17:34:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/how-i-build-small-tools-with-claude-and-what-they-actually-earn-1in1</link>
      <guid>https://dev.to/iamthedev/how-i-build-small-tools-with-claude-and-what-they-actually-earn-1in1</guid>
      <description>&lt;p&gt;"Build an app with AI and earn passively" is the most oversold sentence in developer content right now. I have made about $6,400 from AI-built tools over three years, which is a decent side income, not a passive one. Most of that came from one product, and the work was support, not code.&lt;/p&gt;

&lt;p&gt;This is the honest version: what I built, what it earned, what it cost, and which model I would pick again.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does · Built in&lt;/th&gt;
&lt;th&gt;Gross revenue · Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hotel rate widget&lt;/td&gt;
&lt;td&gt;Embeddable price checker for small travel sites · 3 weeks&lt;/td&gt;
&lt;td&gt;$2,900 · Running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Itinerary PDF export&lt;/td&gt;
&lt;td&gt;Turns a chat into a branded PDF · 10 days&lt;/td&gt;
&lt;td&gt;$1,850 · Sold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meeting notes CLI&lt;/td&gt;
&lt;td&gt;Local transcription and summary · 2 weeks&lt;/td&gt;
&lt;td&gt;$0 · Shut down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trip planner&lt;/td&gt;
&lt;td&gt;Conversational planner with booking links · 4 weeks&lt;/td&gt;
&lt;td&gt;$1,650 · Running&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total gross is about $6,400 over three years. Hosting, API calls, and one design tool cost roughly $1,200. The rest was my time, which I value at more than the margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually sold
&lt;/h2&gt;

&lt;p&gt;The hotel widget. A widget is unglamorous, embeddable, and easy to explain: a small travel site pastes one script tag and gets live rates on their pages.&lt;/p&gt;

&lt;p&gt;Three things made it sell:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It solved a buying question, not a writing question.&lt;/strong&gt; People pay for prices and availability. They rarely pay for a better description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It had a unit of value.&lt;/strong&gt; Ten thousand lookups for a flat fee. The buyer could do arithmetic, and so could I.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It failed loudly.&lt;/strong&gt; When the rate feed was down, the widget said "price unavailable" instead of showing a stale number. Two customers mentioned this as the reason they stayed, which I did not expect.&lt;/p&gt;

&lt;p&gt;The itinerary PDF made money once, as an acquisition: a small agency bought it and paid me to hand over the code. That is a real outcome and not a business.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not sell
&lt;/h2&gt;

&lt;p&gt;The meeting notes CLI earned nothing, and it took me a while to understand why. The free options were good, the paid options were bundled into tools people already had, and my version's only advantage was that it ran locally. That is a feature, not a product.&lt;/p&gt;

&lt;p&gt;The lesson: if the buyer can get 80% of the value for free, the remaining 20% needs to be a workflow, not a checkbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost nobody lists
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Support.&lt;/strong&gt; Roughly three hours a week at the peak, mostly installation problems. A widget is easy; the site it gets embedded in is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API drift.&lt;/strong&gt; Two rate-feed changes in eighteen months. Each one was a day of work and a round of apologies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Billing and tax.&lt;/strong&gt; Stripe is easy. Invoices, refunds, and the one customer who paid twice are not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distribution.&lt;/strong&gt; This is the real cost. Writing the tool took three weeks. Getting the first ten customers took three months, mostly in communities where I was already a member.&lt;/p&gt;

&lt;p&gt;"Passive" describes the revenue after month eight. It does not describe months one through seven.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three models, ranked by fit for a solo developer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Usage-based on a boring B2B need.&lt;/strong&gt; The widget. A buyer with a business reason to pay, a measurable unit, and no expectation of a consumer app. This is the model I would choose every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. One-time sale of the code.&lt;/strong&gt; The PDF export. Lower ceiling, immediate cash, no support tail. Good if you enjoy building more than operating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Consumer subscription.&lt;/strong&gt; The one I have not made work. Churn is brutal, support is emotional, and the buyer compares you to free tools. I would need a much sharper niche than "travel planning" to try again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The travel-specific option most people miss
&lt;/h2&gt;

&lt;p&gt;There is a fourth model that fits small travel tools better than a subscription: take a margin on the transaction instead of charging the user.&lt;/p&gt;

&lt;p&gt;If your tool recommends a hotel and the booking completes through a partner, the partner pays a commission or lets you set a markup by country. The user pays the same as they would elsewhere; you earn on conversion rather than on a paywall.&lt;/p&gt;

&lt;p&gt;That is the model behind RollingGo's partner program. The MCP server returns a &lt;code&gt;bookingUrl&lt;/code&gt; per property, so a small tool can hand off the transaction and still earn on it. Two things make it viable for a solo developer: the API key tier is free with no call cap, so testing costs nothing, and the markup is set on the partner side rather than built into your code.&lt;/p&gt;

&lt;p&gt;It is not passive either. Conversion depends on the quality of your recommendation, which depends on the data and the UI. But it aligns the incentive: you get paid when the user actually books, not when they sign up and forget.&lt;/p&gt;

&lt;p&gt;If you want to test the model without building a product first, the &lt;a href="https://global.rollinggo.store/" rel="noopener noreferrer"&gt;developer site&lt;/a&gt; has the partner flow and the key request. The vendor reports 2,000+ developers and agents connected, which is a useful signal for whether other small builders are trying the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell someone starting today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pick a job with a number in it.&lt;/strong&gt; "Cheaper hotels" is measurable. "Better travel planning" is not, and you will not know if you shipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Charge from day one.&lt;/strong&gt; Free users do not validate a business, they validate your patience. Ten paying customers at $19 tells you more than a thousand free signups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the surface small.&lt;/strong&gt; One widget, one endpoint, one price. Every additional feature doubles the support load and adds a failure mode you have to explain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write the cancellation email before launch.&lt;/strong&gt; If you cannot describe how a customer leaves, you do not understand the product yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not quit the job.&lt;/strong&gt; $6,400 over three years is a good reason to keep building and a bad reason to stop earning a salary.&lt;/p&gt;

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

&lt;p&gt;AI made the building cheap. Three years ago the widget was six weeks of work; now it is closer to two, and most of that is the embed logic, not the model.&lt;/p&gt;

&lt;p&gt;What did not get cheaper is deciding what to build, finding ten people who will pay, and answering their email when it breaks. Those are the parts that decide whether a tool earns $0 or $6,000, and no model has made them passive.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>sideprojects</category>
      <category>discuss</category>
    </item>
    <item>
      <title>12 Hidden MCP Servers That Are Actually Useful</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Mon, 09 Nov 2026 04:19:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/12-hidden-mcp-servers-that-are-actually-useful-595i</link>
      <guid>https://dev.to/iamthedev/12-hidden-mcp-servers-that-are-actually-useful-595i</guid>
      <description>&lt;p&gt;"Hidden" is doing a lot of work in most lists like this. They mean "not the six everyone repeats", and then they recommend a server with 40,000 stars that appeared in every roundup last month.&lt;/p&gt;

&lt;p&gt;My rule for this list: under 6,000 stars, actively maintained, and doing a job that comes up in a normal week. That filters out both the overexposed and the abandoned. Two servers I wanted to include were archived, one had no commit since 2025, and those are the reasons they are not here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Job · Maintainer&lt;/th&gt;
&lt;th&gt;Stars · Roundup citation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;Query a staging DB · crystaldba&lt;/td&gt;
&lt;td&gt;3,429 · Nimbalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;Dashboards and alerts · Grafana Labs&lt;/td&gt;
&lt;td&gt;3,611 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tavily&lt;/td&gt;
&lt;td&gt;Search answers with sources · Tavily&lt;/td&gt;
&lt;td&gt;2,461 · Composio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;Provider and module docs · HashiCorp&lt;/td&gt;
&lt;td&gt;1,554 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qdrant&lt;/td&gt;
&lt;td&gt;Vector search debugging · Qdrant&lt;/td&gt;
&lt;td&gt;1,556 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DuckDuckGo&lt;/td&gt;
&lt;td&gt;Free web search · community&lt;/td&gt;
&lt;td&gt;1,553 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exa&lt;/td&gt;
&lt;td&gt;Semantic web research · Exa Labs&lt;/td&gt;
&lt;td&gt;5,177 · Nimbalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;Document store queries · MongoDB&lt;/td&gt;
&lt;td&gt;1,150 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;td&gt;Runtime error context · Sentry&lt;/td&gt;
&lt;td&gt;948 · AY Automate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elasticsearch&lt;/td&gt;
&lt;td&gt;Log and index search · Elastic&lt;/td&gt;
&lt;td&gt;725 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Cache and key inspection · Redis&lt;/td&gt;
&lt;td&gt;641 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MotherDuck&lt;/td&gt;
&lt;td&gt;DuckDB in the cloud · MotherDuck&lt;/td&gt;
&lt;td&gt;535 · none found&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six of the twelve have no third-party citation. Vendor-maintained and recently shipped is the claim I can defend, so that is the claim I am making.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four I reach for weekly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Postgres (crystaldba)
&lt;/h3&gt;

&lt;p&gt;Read-only SQL against staging with a schema explorer. The questions it answers are the ones that cause silent bugs: which columns are nullable, which index exists, whether a foreign key actually points where the diagram says.&lt;/p&gt;

&lt;p&gt;3,429 stars, MIT, community-maintained, cited by Nimbalyst. If your database is Supabase, use their official server instead; it is more integrated and equally safe when scoped correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Grafana
&lt;/h3&gt;

&lt;p&gt;Dashboards, alert rules, and PromQL queries. During an incident, the agent can ask "what changed at 14:02" and read the same dashboard you would open.&lt;/p&gt;

&lt;p&gt;3,611 stars, vendor-maintained. No roundup citation, which surprises me given how many teams run Grafana. This is the kind of tool that gets used and never written about.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Sentry
&lt;/h3&gt;

&lt;p&gt;Issue details, stack traces, breadcrumbs, and release info. 948 stars and one of the highest value-to-star ratios on the list.&lt;/p&gt;

&lt;p&gt;Vendor-maintained, cited by AY Automate and GET Claude Skills. The grouping is the underrated part: the model stops treating 400 events as 400 problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Tavily
&lt;/h3&gt;

&lt;p&gt;Search with short answers and sources, designed for agents rather than humans. Cheaper than crawling when you need a fact, worse than Firecrawl when you need a page.&lt;/p&gt;

&lt;p&gt;2,461 stars. Composio lists it as a default search server in its Codex setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure and data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5. Terraform (HashiCorp)
&lt;/h3&gt;

&lt;p&gt;Registry lookups, provider docs, and module examples. "What arguments does this resource take in the current provider version" is a question models get wrong constantly, because provider schemas change every few weeks.&lt;/p&gt;

&lt;p&gt;1,554 stars, MPL-2.0, vendor-maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Qdrant
&lt;/h3&gt;

&lt;p&gt;Vector search over your own collections. I use it for RAG debugging: given a bad answer, check whether retrieval returned the right chunk before blaming the model.&lt;/p&gt;

&lt;p&gt;1,556 stars, Apache-2.0, vendor-maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. MongoDB
&lt;/h3&gt;

&lt;p&gt;Collections, indexes, aggregation pipelines, and schema sampling. Document stores punish guessing more than relational ones, because there is no schema to read from the code.&lt;/p&gt;

&lt;p&gt;1,150 stars, Apache-2.0, vendor-maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Elasticsearch
&lt;/h3&gt;

&lt;p&gt;Index mapping, query DSL, and log search. The mapping question alone, "is this field analyzed or keyword", explains a class of search bugs that look like application logic.&lt;/p&gt;

&lt;p&gt;725 stars, Apache-2.0, vendor-maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Redis
&lt;/h3&gt;

&lt;p&gt;Key inspection, TTLs, and memory usage by prefix. Useful when the bug is "the cache is stale" and nobody wants to open a CLI at 11 p.m.&lt;/p&gt;

&lt;p&gt;641 stars, MIT, vendor-maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. MotherDuck
&lt;/h3&gt;

&lt;p&gt;DuckDB in the cloud: run analytical SQL against a warehouse without exporting a CSV first. 535 stars, MIT.&lt;/p&gt;

&lt;p&gt;This is the smallest project on the list and the one I use least, but when the question is "what do the numbers actually say", it saves a round trip through a BI tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research
&lt;/h2&gt;

&lt;h3&gt;
  
  
  11. Exa
&lt;/h3&gt;

&lt;p&gt;Semantic search that returns extracted page content. Better than keyword search for "find the changelog entry that renamed this option".&lt;/p&gt;

&lt;p&gt;5,177 stars, MIT, Nimbalyst's fifth pick for Codex. The failure mode is over-fetching; two results, not ten.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. DuckDuckGo
&lt;/h3&gt;

&lt;p&gt;Free, no API key, no account. When you need one page and do not want to wire a paid provider, this is the server that gets you there in a minute.&lt;/p&gt;

&lt;p&gt;1,553 stars, community-maintained. Quality is below Tavily and Exa; the price is zero and so is the setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I left out, and why
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Archived repositories.&lt;/strong&gt; Two popular servers I wanted to include are archived. An archived server still works today and will not work after the next API change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anything that needed a paid plan to run one call.&lt;/strong&gt; I test before I recommend. If the first call requires procurement, it is not a hidden gem, it is a sales funnel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Servers with no commit in a year.&lt;/strong&gt; MCP moved a lot in 2026. Stale is a synonym for broken in a few months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The big six.&lt;/strong&gt; Context7, Playwright, GitHub, Figma, Firecrawl, and Chrome DevTools are excellent and appear in every other list. Excluding them is the point of this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to add one without wrecking tool selection
&lt;/h2&gt;

&lt;p&gt;Three rules from a year of trial and error:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One server, one job.&lt;/strong&gt; Two search servers means the model picks one at random and you cannot tell which.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project scope.&lt;/strong&gt; A global config is convenient until three repos disagree about which database is "prod".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read-only credentials.&lt;/strong&gt; Every server above that touches data starts with a read-only role. The one time I skipped that, the agent helpfully "fixed" a staging table during a demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one first
&lt;/h2&gt;

&lt;p&gt;If you are on call: Sentry, then Grafana, then Postgres. That trio answers "what broke, when did it change, and what does the data say" without a single dashboard tab.&lt;/p&gt;

&lt;p&gt;If you are doing data work: Postgres, then Qdrant, then MotherDuck.&lt;/p&gt;

&lt;p&gt;If you are building a product with a transactional step, none of these will help, and that is a different category of server entirely. A read-only tool makes your agent accurate. A transactional one makes it useful, and it needs a much stricter review of what it is allowed to do. The one I use for that is the &lt;a href="https://github.com/RollingGo-AI/RollingGo-Hotel-MCP-Global" rel="noopener noreferrer"&gt;RollingGo Hotel MCP server&lt;/a&gt;, which returns real rates and a booking link instead of a description.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tooling</category>
    </item>
    <item>
      <title>9 MCP Servers That Make Your Coding Agent More Accurate</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Sat, 07 Nov 2026 17:53:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/9-mcp-servers-that-make-your-coding-agent-more-accurate-1851</link>
      <guid>https://dev.to/iamthedev/9-mcp-servers-that-make-your-coding-agent-more-accurate-1851</guid>
      <description>&lt;p&gt;Accuracy in a coding agent is not a model setting. It is the difference between the model guessing and the model checking.&lt;/p&gt;

&lt;p&gt;I ran the same twelve tasks against a bare agent and an agent with nine MCP servers attached: find a symbol's callers, fix a failing test, answer "what version of this API", explain a production error, and six others. The bare agent was fluent and wrong on five of the twelve. The grounded agent was slower by about a second per task and wrong once.&lt;/p&gt;

&lt;p&gt;These are the nine servers that moved the number, ranked by how much of the gap they closed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Main job · Maintainer&lt;/th&gt;
&lt;th&gt;Stars · Accuracy failure it removes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Context7&lt;/td&gt;
&lt;td&gt;Library docs · Upstash&lt;/td&gt;
&lt;td&gt;63,751 · Wrong API for the installed version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serena&lt;/td&gt;
&lt;td&gt;Symbol navigation · Oraios&lt;/td&gt;
&lt;td&gt;30,750 · Edits in the wrong place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;Repo and CI state · GitHub&lt;/td&gt;
&lt;td&gt;33,993 · Guessing what CI said&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;td&gt;Runtime errors · Sentry&lt;/td&gt;
&lt;td&gt;948 · Inventing a stack trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;Schema truth · crystaldba&lt;/td&gt;
&lt;td&gt;3,429 · Columns that do not exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;Real browser · Microsoft&lt;/td&gt;
&lt;td&gt;38,686 · "It should work"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;Live web · Firecrawl&lt;/td&gt;
&lt;td&gt;7,698 · Stale docs and blog posts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exa&lt;/td&gt;
&lt;td&gt;Research · Exa Labs&lt;/td&gt;
&lt;td&gt;5,177 · Confident uncited claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RollingGo Hotel MCP&lt;/td&gt;
&lt;td&gt;Live prices · RollingGo&lt;/td&gt;
&lt;td&gt;86 · Invented inventory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Context7: the version problem
&lt;/h2&gt;

&lt;p&gt;Most wrong answers I see are not hallucinations. They are correct answers for a version that is two years old.&lt;/p&gt;

&lt;p&gt;Context7 fetches documentation for the version you name. The difference is small and decisive: &lt;code&gt;client.responses.create()&lt;/code&gt; instead of a deprecated call, a config key that still exists, an import path that moved in the last major release.&lt;/p&gt;

&lt;p&gt;It is the highest-starred server here and appears in four independent roundups. Enable it per project; leaving it on for stable libraries just costs context.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Serena: symbol-level navigation
&lt;/h2&gt;

&lt;p&gt;Grepping for &lt;code&gt;createSession&lt;/code&gt; in a 12-service monorepo returns 40 hits, and the model picks one. Serena talks to a language server, so it can find the definition, the references, and the exact range to edit.&lt;/p&gt;

&lt;p&gt;This is the server that most reduced "edited the wrong function" in my test. It is also the fussiest to install, because it needs a language server per language. Worth an hour of setup on a codebase you will touch for months.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. GitHub: state instead of assumption
&lt;/h2&gt;

&lt;p&gt;Half of agent inaccuracy is about the world outside the file: did CI pass, what did the reviewer ask for, which branch is this. The GitHub server answers those questions from the API.&lt;/p&gt;

&lt;p&gt;Read-heavy use. The write tools exist and I keep them off by default, because an agent that can comment on PRs will eventually comment something wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Sentry: errors with evidence
&lt;/h2&gt;

&lt;p&gt;A stack trace, breadcrumbs, the release it appeared in, and grouping so 400 events stay one problem.&lt;/p&gt;

&lt;p&gt;The accuracy gain is subtle: without it, the model reconstructs a plausible failure from the code. With it, the model reads the actual line that threw.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Postgres: schema truth
&lt;/h2&gt;

&lt;p&gt;A read-only connection to staging answers the questions that cause silent bugs: which columns are nullable, what the index is, how many rows point at a deleted user.&lt;/p&gt;

&lt;p&gt;I scope it to a read-only role and a staging database. A production credential in a config file is how a debugging session becomes an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Playwright: the browser as ground truth
&lt;/h2&gt;

&lt;p&gt;Front-end agents are confidently wrong about layout and flow. Playwright reads the accessibility tree, clicks by role, and reports what changed.&lt;/p&gt;

&lt;p&gt;It closed the "works in the test, broken in the browser" gap in my tasks. One caveat: it is slow, and slow tools make agents impatient, so keep the step budget generous when it is enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Firecrawl: the live web
&lt;/h2&gt;

&lt;p&gt;Docs, changelogs, release notes, and the issue thread where someone hit the same bug. Returns markdown, not links, so the model reads the page instead of guessing from a title.&lt;/p&gt;

&lt;p&gt;The failure mode is context bloat. Crawl one page, not a site, unless the question is about the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Exa: research with retrieval
&lt;/h2&gt;

&lt;p&gt;Semantic search that returns extracted content. For "what changed in this library between versions", it finds the changelog and the discussion, which is often better than the official docs.&lt;/p&gt;

&lt;p&gt;Pairs badly with a small context window; keep the result count low and the query specific.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. RollingGo Hotel MCP: the domain that cannot be guessed
&lt;/h2&gt;

&lt;p&gt;I include this because it is the clearest case of a problem no prompt can fix. Ask a model for hotel prices and it produces numbers that were true at training time, for hotels that may have closed.&lt;/p&gt;

&lt;p&gt;The server returns &lt;code&gt;searchHotels&lt;/code&gt;, &lt;code&gt;getHotelDetail&lt;/code&gt;, and &lt;code&gt;getHotelSearchTags&lt;/code&gt;, with a hotel ID, currency, nightly rate, and booking link in one payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honest evidence: 86 stars, MIT, vendor-maintained, no third-party roundup citation. The vendor reports 2,000+ developers and agents connected, 2M+ properties, and 110,000 direct contracts. It is on this list for a capability, not a popularity contest: it is the only one here that can turn a guess into a lookup for something a user will spend money on. The &lt;a href="https://global.rollinggo.store/" rel="noopener noreferrer"&gt;developer site&lt;/a&gt; has the key, the schema, and the response examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the twelve tasks taught me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Grounding beats prompting.&lt;/strong&gt; The five failures on the bare agent were all facts a server could have supplied. Rewriting the prompt moved one of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One server per failure mode.&lt;/strong&gt; Adding a second search server made accuracy worse, not better, because tool selection became ambiguous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency is the tax.&lt;/strong&gt; About a second per grounded task. I would pay ten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errors must be visible.&lt;/strong&gt; A server that returns an empty list on failure makes the agent sound confident. Prefer the one that returns a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  A setup that fits in one config
&lt;/h2&gt;

&lt;p&gt;Four servers cover most of the gap: Context7 for docs, Serena for code, GitHub for state, Sentry for runtime. Add Postgres and Playwright if your work touches data or UI.&lt;/p&gt;

&lt;p&gt;Nine is the ceiling for me, and I rarely run all nine at once. Tool count above roughly 25 makes selection worse, which shows up as accuracy loss that looks like a model problem and is not.&lt;/p&gt;

&lt;p&gt;The test I would run on your own setup: take five tasks your agent got wrong last month, attach one server at a time, and see which one changes the answer. That is a better use of an afternoon than any benchmark.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>productivity</category>
    </item>
    <item>
      <title>10 Codex Plugins You Wish You Knew Sooner (All Checked)</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Sat, 07 Nov 2026 04:31:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/10-codex-plugins-you-wish-you-knew-sooner-all-checked-1moh</link>
      <guid>https://dev.to/iamthedev/10-codex-plugins-you-wish-you-knew-sooner-all-checked-1moh</guid>
      <description>&lt;p&gt;Codex plugins landed in early 2026, and the directory filled up fast enough that most "top 10" posts are now a mix of official plugins, MCP servers in disguise, and skill packs that predate the plugin spec.&lt;/p&gt;

&lt;p&gt;That mix is fine, as long as the article says which is which. I checked each entry below against OpenAI's curated directory or the maintainer's own repository, and I cite the roundup where another publication recommends the same thing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;What it is · Maintainer&lt;/th&gt;
&lt;th&gt;Adoption signal · Also recommended by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;App connector · GitHub&lt;/td&gt;
&lt;td&gt;33,993 stars (MCP repo) · OpenAI curated directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Figma&lt;/td&gt;
&lt;td&gt;App connector · Figma&lt;/td&gt;
&lt;td&gt;16,077 stars (community MCP) · OpenAI curated, Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;App connector · Linear&lt;/td&gt;
&lt;td&gt;hosted, no repo · OpenAI curated, Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;td&gt;App connector · Sentry&lt;/td&gt;
&lt;td&gt;948 stars (MCP repo) · OpenAI curated, AY Automate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context7&lt;/td&gt;
&lt;td&gt;MCP + skill · Upstash&lt;/td&gt;
&lt;td&gt;63,751 stars · Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;MCP + skills · Firecrawl&lt;/td&gt;
&lt;td&gt;7,698 stars (MCP repo) · Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Superpowers&lt;/td&gt;
&lt;td&gt;Workflow framework · Jesse Vincent&lt;/td&gt;
&lt;td&gt;1,009,371 installs (Claude marketplace) · Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matt Pocock's skills&lt;/td&gt;
&lt;td&gt;Skill pack · Matt Pocock&lt;/td&gt;
&lt;td&gt;Firecrawl reports ~125k stars (Jun 2026) · Firecrawl, aihero.dev&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitNexus&lt;/td&gt;
&lt;td&gt;Code graph + MCP · Abhigyan Patwari&lt;/td&gt;
&lt;td&gt;Firecrawl reports ~41.9k stars (Jun 2026) · Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTK&lt;/td&gt;
&lt;td&gt;Output compression proxy · rtk-ai&lt;/td&gt;
&lt;td&gt;Firecrawl reports ~61.3k stars (Jun 2026) · Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two notes on that table. The install flow is &lt;code&gt;/plugins&lt;/code&gt; inside Codex CLI, and &lt;code&gt;@plugin-name&lt;/code&gt; to invoke one explicitly. And the star numbers attributed to Firecrawl are from their June 2026 roundup, not a live count, because I would rather label a number than pretend it is current.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four official ones worth installing first
&lt;/h2&gt;

&lt;p&gt;These come from OpenAI's curated directory, which means the vendor maintains the integration and the auth flow is managed for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. GitHub
&lt;/h3&gt;

&lt;p&gt;Review diffs, read issues, check workflow runs without leaving the session. The connector covers the parts of GitHub you actually use during a change, and it is the one I install on every machine.&lt;/p&gt;

&lt;p&gt;Where it earns its place: CI failures on a branch I did not touch. The agent reads the run log and tells me which dependency changed, which used to be a fifteen-minute investigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Figma
&lt;/h3&gt;

&lt;p&gt;Reads frames, layer names, text styles, and spacing from a real file. The output quality depends almost entirely on how the file is structured; named layers and auto-layout give usable code, loose frames give a first draft.&lt;/p&gt;

&lt;p&gt;Firecrawl's roundup lists it, and the failure mode is worth repeating: updating an existing component often triggers full regeneration rather than a diff.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Linear
&lt;/h3&gt;

&lt;p&gt;Pulls a ticket, updates its state, links the PR. The real benefit is continuity: a week later, the agent can summarize what shipped and what is blocked from actual issue state rather than your memory.&lt;/p&gt;

&lt;p&gt;Depends on ticket quality. A vague ticket produces a vague implementation, and no plugin fixes that.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sentry
&lt;/h3&gt;

&lt;p&gt;Stack traces, breadcrumbs, release info, and grouping. 948 stars is low for how much time it saves, and the reason is boring: it is a vendor repository, not a community project that chased stars.&lt;/p&gt;

&lt;p&gt;Pair it with the GitHub connector and an incident becomes three calls: which error, which release, which commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four that change how the agent works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5. Context7
&lt;/h3&gt;

&lt;p&gt;Version-specific library docs, fetched at call time. The highest-starred entry here, and the one that most directly reduces wrong API calls.&lt;/p&gt;

&lt;p&gt;Firecrawl's roundup lists it, with the caveat I agree with: leave it on for every prompt and it burns context on libraries the model already knows. Enable it per project.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Firecrawl
&lt;/h3&gt;

&lt;p&gt;Search, scrape, crawl, and map, returning clean markdown instead of raw HTML. Large results go to files, which keeps the context window from filling with boilerplate.&lt;/p&gt;

&lt;p&gt;The plugin includes skills that teach Codex when to reach for each tool, which is the part most MCP servers skip. Without that, the agent picks &lt;code&gt;scrape&lt;/code&gt; for a question that needed &lt;code&gt;search&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Superpowers
&lt;/h3&gt;

&lt;p&gt;A workflow framework rather than a tool: brainstorm before code, write a failing test first, investigate root cause before fixing, hand implementation to subagents with a review after each task.&lt;/p&gt;

&lt;p&gt;It started in the Claude ecosystem and now ships Codex support with tool mappings of its own. 1,009,371 installs on the Claude marketplace, and Firecrawl lists it for Codex. The cost is verbosity: some sessions produce more planning text than code.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Matt Pocock's skills
&lt;/h3&gt;

&lt;p&gt;A skill collection built around four failure modes: misalignment, verbosity, broken code, and architecture that turns into mud. The headliners are &lt;code&gt;grill-me&lt;/code&gt; (interviews you before any code), &lt;code&gt;tdd&lt;/code&gt;, &lt;code&gt;diagnose&lt;/code&gt;, and &lt;code&gt;handoff&lt;/code&gt; (compresses a session into a document for the next one).&lt;/p&gt;

&lt;p&gt;It predates the plugin spec, installs with &lt;code&gt;npx skills add&lt;/code&gt;, and shows up under "Created by you". Firecrawl reports about 125k stars, which would make it the most-starred skill pack in this list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two that fix context problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  9. GitNexus
&lt;/h3&gt;

&lt;p&gt;Builds a knowledge graph of a repository in the browser and lets the agent query it with Graph RAG. The questions it answers are the ones file search handles badly: who calls this function, which modules depend on that one, what is dead code.&lt;/p&gt;

&lt;p&gt;Client-side, so private repos stay private. The limit is size: very large monorepos push browser memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. RTK
&lt;/h3&gt;

&lt;p&gt;A Rust proxy between Codex and your shell that compresses command output before it reaches the context. &lt;code&gt;git diff&lt;/code&gt;, &lt;code&gt;kubectl describe&lt;/code&gt;, and failed install logs are the biggest token sinks in a normal session, and RTK reports 60 to 90 percent savings on routine output.&lt;/p&gt;

&lt;p&gt;It works at the shell layer, so it also helps Claude Code, Cursor, and anything else that runs commands. The trade-off: compressed output sometimes drops a line you needed, and you have to know when to disable it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one I added myself
&lt;/h2&gt;

&lt;p&gt;Every Codex setup I build ends up needing one domain tool that no marketplace covers. Mine is travel, so I wrapped an MCP server as a plugin and loaded it through &lt;code&gt;config.toml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mcp_servers&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rollinggo-hotel&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;
&lt;span class="n"&gt;http_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives Codex three tools: &lt;code&gt;searchHotels&lt;/code&gt;, &lt;code&gt;getHotelDetail&lt;/code&gt;, &lt;code&gt;getHotelSearchTags&lt;/code&gt;. The reason to put it in Codex rather than a separate script is that the agent can check a price while it is editing the page that displays prices.&lt;/p&gt;

&lt;p&gt;Honest evidence, since I have asked it of every other entry: 86 stars, MIT licensed, vendor-maintained, and no third-party roundup citation yet. The vendor reports 2,000+ developers and agents connected, 2M+ properties, and 110,000 direct contracts. It earns its slot on capability, not popularity, and the &lt;a href="https://github.com/RollingGo-AI/RollingGo-Hotel-MCP-Global" rel="noopener noreferrer"&gt;repository&lt;/a&gt; has the config, tool schemas, and response examples if you want to test that claim yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose, given ten options
&lt;/h2&gt;

&lt;p&gt;Three questions, in order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it maintained by the service owner?&lt;/strong&gt; GitHub, Figma, Linear, Sentry, Context7 (for its own index), and Firecrawl all are. That matters because the API changes are their problem, not yours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it replace a tab or a decision?&lt;/strong&gt; A plugin that saves a tab is nice. A plugin that changes what the agent knows is worth the context it costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does it add to every turn?&lt;/strong&gt; Plugin descriptions load on every turn whether or not they fire. Four plugins is my ceiling for a coding session; beyond that, tool selection gets worse before it gets faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would skip
&lt;/h2&gt;

&lt;p&gt;Autonomous "fix everything" plugins, anything that edits tests to make them pass, and collections with 200 skills where 190 never fire. The directory has enough good options now that volume is no longer a virtue.&lt;/p&gt;

&lt;p&gt;If you want the setup I run, it is four official connectors, Context7, and one domain server. That is the whole config, and it covers 90% of a normal week.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>15 Cheap Travel Hacks in 2026 That Actually Hold Up</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Thu, 05 Nov 2026 17:07:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/15-cheap-travel-hacks-in-2026-that-actually-hold-up-mf</link>
      <guid>https://dev.to/iamthedev/15-cheap-travel-hacks-in-2026-that-actually-hold-up-mf</guid>
      <description>&lt;p&gt;Most "cheap travel" lists are the same five tips with a new headline: book on Tuesday, use incognito mode, set a price alert. Two of those do nothing, and the rest are half-true.&lt;/p&gt;

&lt;p&gt;I travel about 20 weeks a year for work and take two longer trips on my own money. These are the 15 tactics that have survived, sorted by what they actually save: money, time, or a mistake.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hack&lt;/th&gt;
&lt;th&gt;Saves&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shift the trip by one day&lt;/td&gt;
&lt;td&gt;10-25% on flights&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price the airport transfer before choosing the airport&lt;/td&gt;
&lt;td&gt;20-60 EUR&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Book the positioning leg separately&lt;/td&gt;
&lt;td&gt;15-30%&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Check the hotel's own site after an aggregator&lt;/td&gt;
&lt;td&gt;0-12%&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Book refundable, then rebook if the price drops&lt;/td&gt;
&lt;td&gt;5-15%&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pay in the property's currency&lt;/td&gt;
&lt;td&gt;2-4%&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Travel three weeks after peak&lt;/td&gt;
&lt;td&gt;20-40%&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use a long layover on purpose&lt;/td&gt;
&lt;td&gt;One hotel night&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Take the night train&lt;/td&gt;
&lt;td&gt;One hotel night&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status match instead of status grind&lt;/td&gt;
&lt;td&gt;Breakfast, upgrades&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do the breakfast math&lt;/td&gt;
&lt;td&gt;10-20 EUR a day&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add the city tax and resort fee before comparing&lt;/td&gt;
&lt;td&gt;Avoids a surprise&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ask for the room, not the discount&lt;/td&gt;
&lt;td&gt;Upgrades&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price the whole trip, not the flight&lt;/td&gt;
&lt;td&gt;Avoids false savings&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make the AI show its source&lt;/td&gt;
&lt;td&gt;Avoids fake prices&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Shift the trip by one day
&lt;/h2&gt;

&lt;p&gt;The difference between a Friday and a Saturday departure is often 20% on the same route, and it is not a secret trick, it is demand. I check a three-day window before I commit to dates.&lt;/p&gt;

&lt;p&gt;The trap: shifting the outbound can move you into a more expensive return. Price the pair, not the leg.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Price the transfer before you pick the airport
&lt;/h2&gt;

&lt;p&gt;The cheap airport is not the cheap arrival. A 15 EUR fare difference disappears when the transfer costs 45 EUR and takes 70 minutes.&lt;/p&gt;

&lt;p&gt;I do this arithmetic before booking, every time: ticket + transfer + time. The "expensive" city airport wins more often than the fare screen suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Book the positioning leg separately
&lt;/h2&gt;

&lt;p&gt;If you live in a secondary city, a separate short hop to a hub can undercut a through-fare. The risk is real: if the first flight is late, the second airline owes you nothing.&lt;/p&gt;

&lt;p&gt;I only do this with a same-day buffer of four hours and hand luggage, or an overnight where the hotel is cheaper than the fare difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check the hotel's own site after the aggregator
&lt;/h2&gt;

&lt;p&gt;Rate parity is mostly enforced and occasionally not. I find the property on an aggregator, then check the property's own site for a member rate, a longer-stay rate, or a package that includes breakfast.&lt;/p&gt;

&lt;p&gt;The win is often not the price but the cancellation terms. A direct booking is also easier to change when plans move, because you are talking to the people who own the room.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Book refundable, then rebook if the price drops
&lt;/h2&gt;

&lt;p&gt;A refundable rate costs 5-10% more and gives you a free option. I book it when the trip is more than six weeks out, set a price alert, and rebook if the drop beats the difference.&lt;/p&gt;

&lt;p&gt;This only works if you actually check. I put a reminder in the calendar for 21 days out, which is when the curve usually turns.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Pay in the property's currency
&lt;/h2&gt;

&lt;p&gt;When a card terminal or a payment page offers to convert to your home currency, decline. The markup is typically 2-4%, and it is invisible because the number looks familiar.&lt;/p&gt;

&lt;p&gt;The same applies online: choose the local currency at checkout and let your bank do the conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Travel three weeks after peak
&lt;/h2&gt;

&lt;p&gt;The week after a school holiday ends is the best value in travel. Prices fall before demand recovers, the weather is usually still good, and the popular places are 30% emptier.&lt;/p&gt;

&lt;p&gt;I plan one trip a year around this window rather than around a specific date. It is the single biggest saving on this list and it requires no tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Use a long layover on purpose
&lt;/h2&gt;

&lt;p&gt;Some hubs let you extend a connection into a stopover, and some cities are worth six hours. A deliberate 10-hour layover can replace a hotel night and a separate flight.&lt;/p&gt;

&lt;p&gt;The conditions: airport with luggage storage or a left-luggage desk, a train or shuttle into the centre under 40 minutes, and a return buffer of two hours. Without all three, it is stress, not savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Take the night train
&lt;/h2&gt;

&lt;p&gt;Where they still exist, night trains replace a hotel night and a travel day at once. A sleeper berth often costs less than a mid-range hotel plus the daytime ticket.&lt;/p&gt;

&lt;p&gt;I book a private compartment when the trip is longer than nine hours. In a shared couchette, you save money and lose the next day to bad sleep, which is not a good trade on a work trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Status match instead of status grind
&lt;/h2&gt;

&lt;p&gt;Hotel and airline programmes will match a competitor's status for a challenge period. One email with a screenshot of your current tier often lands breakfast and late checkout for a year.&lt;/p&gt;

&lt;p&gt;It is the least-known and most useful loyalty tactic. The worst outcome is a polite no.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Do the breakfast math
&lt;/h2&gt;

&lt;p&gt;A 28 EUR hotel breakfast for two, times four mornings, is 112 EUR. A café two blocks away is 20 EUR for the same thing, and usually better.&lt;/p&gt;

&lt;p&gt;Hotel breakfast is worth it when it is included in a rate that is only slightly higher, or when the alternative is a 20-minute walk in the rain. Do the multiplication once per trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Add the city tax and resort fee before comparing
&lt;/h2&gt;

&lt;p&gt;The headline price is not the price. City tax per person per night, resort fees, and service charges can add 15-25% in some destinations.&lt;/p&gt;

&lt;p&gt;Before I compare two hotels, I add the mandatory extras and the transfer. The cheaper headline is wrong often enough that I stopped trusting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Ask for the room, not the discount
&lt;/h2&gt;

&lt;p&gt;Front desk staff usually cannot change the rate but can change the room. A polite ask at check-in, "is there anything higher available", works more often than a request for money off.&lt;/p&gt;

&lt;p&gt;It costs nothing and takes ten seconds. On a long stay, ask on day two instead of day one, when the desk has a better view of inventory.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. Price the whole trip, not the flight
&lt;/h2&gt;

&lt;p&gt;A cheap flight to a far airport with an expensive transfer, a hotel in a transport desert, and three taxi rides a day is not a cheap trip.&lt;/p&gt;

&lt;p&gt;I write down five numbers before booking: transport in, transport out, nightly rate with tax, food estimate, and one activity. The column that looks cheapest on the flight screen rarely wins after the fifth number.&lt;/p&gt;

&lt;h2&gt;
  
  
  15. Make the AI show its source
&lt;/h2&gt;

&lt;p&gt;This is the newest one on the list, and the one that saves the most avoidable mistakes.&lt;/p&gt;

&lt;p&gt;An AI travel assistant without live inventory will produce a hotel name that sounds right and a price that was true two years ago. The fix is not a better prompt. It is an assistant connected to actual inventory, which returns a hotel ID, a currency, a rate, and a booking link that agree with each other.&lt;/p&gt;

&lt;p&gt;When I build one, the rule is simple: every number on screen must be traceable to a tool call, and every price carries the time it was checked. If the assistant cannot show both, the number should not be there.&lt;/p&gt;

&lt;p&gt;The server I use for that is RollingGo Hotel MCP. Three tools cover search, room detail, and the tag vocabulary, and the rate comes back with a booking link rather than a description. It is free at the API key tier with no call cap, covers 2M+ properties, and the vendor reports 2,000+ developers and agents connected. If you are building your own planner, the &lt;a href="https://global.rollinggo.store/" rel="noopener noreferrer"&gt;developer site&lt;/a&gt; is where the docs and key live.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I stopped doing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Incognito mode.&lt;/strong&gt; Prices are not personalized in the way the myth claims. The variance you see is usually inventory and caching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Booking on a specific weekday.&lt;/strong&gt; The day you search matters more than the day you book. Tuesday departures are cheaper; Tuesday purchases are not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chasing the last 5%.&lt;/strong&gt; A fare that requires three connections, a separate ticket, and a 4 a.m. start is not a saving. It is a transfer of cost to your body.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trusting a headline price.&lt;/strong&gt; Every number in this post is about the total, not the sticker. That habit alone is worth more than any single hack above.&lt;/p&gt;

</description>
      <category>travel</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Step-by-Step: A Travel Agent That Can Actually Book</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Thu, 05 Nov 2026 04:48:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/step-by-step-a-travel-agent-that-can-actually-book-1l5b</link>
      <guid>https://dev.to/iamthedev/step-by-step-a-travel-agent-that-can-actually-book-1l5b</guid>
      <description>&lt;p&gt;Most travel agent tutorials end at "the model suggested a hotel". That is the easy 20%. The other 80% is making the suggestion real: a hotel that exists, a price that is current, and a link a user can click without wondering whether it will work.&lt;/p&gt;

&lt;p&gt;This is the build I use now, in eight steps, from an empty folder to a working handoff. It takes an afternoon if you already have an API key, and it deliberately skips payments, because handling payments is the step where side projects go to die.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Decide what "book" means for you
&lt;/h2&gt;

&lt;p&gt;Three models exist, and they are not interchangeable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;You handle · User experience&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redirect&lt;/td&gt;
&lt;td&gt;Nothing · Clicks through to the provider&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assisted&lt;/td&gt;
&lt;td&gt;Order creation · Confirms in your UI, provider completes&lt;/td&gt;
&lt;td&gt;Weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Payment, cancellation, reconciliation · Everything in-app&lt;/td&gt;
&lt;td&gt;Months&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I start with redirect every time. The agent recommends, the provider completes. It is honest, it needs no PCI conversation, and it is enough to validate whether anyone wants the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Get a key and connect the server
&lt;/h2&gt;

&lt;p&gt;The server speaks streamable HTTP at one endpoint. The key comes from the partner portal, and the free tier has no call cap, which matters while you are testing a loop that might call the same tool forty times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it before writing any agent code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://mcp.rollinggo.ai/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json, text/event-stream"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","method":"tools/list","id":1}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns three tools, the rest is application code. If it returns 400, you are missing the &lt;code&gt;Accept&lt;/code&gt; header, which is the most common first failure and the least obvious one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Ask for tags before asking for hotels
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;getHotelSearchTags&lt;/code&gt; is the step everyone skips, and it is why agents invent filters like "rooftop pool".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call getHotelSearchTags and list the tags that relate to families.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache the result for the session, then map the user's words onto real tags. This one call removes a category of nonsense results, and it costs a few milliseconds once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Search with constraints, not vibes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"searchHotels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"originQuery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"family hotel near Tokyo Station with a pool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"place"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tokyo Station"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"placeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"train_station"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checkInParam"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"checkInDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"stayNights"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"adultCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"filterOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"starRatings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hotelTags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"maxPricePerNight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"localeParam"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two arguments are doing the heavy lifting. &lt;code&gt;placeType&lt;/code&gt; keeps "Tokyo Station" from being geocoded as a city, and &lt;code&gt;localeParam.currency&lt;/code&gt; stops you from formatting yen as dollars in the UI. Both are server-side, so you are not writing a converter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Render results as cards, not prose
&lt;/h2&gt;

&lt;p&gt;The response gives you &lt;code&gt;hotelName&lt;/code&gt;, &lt;code&gt;starRating&lt;/code&gt;, &lt;code&gt;priceInfo&lt;/code&gt;, &lt;code&gt;address&lt;/code&gt;, and &lt;code&gt;bookingUrl&lt;/code&gt;. Render those five things. A paragraph that says "a charming option near the station" is worse than a card with a price and a button, because the card can be checked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The Tokyo Station Hotel ★★★★★
Marunouchi, 1-9-1 · from 198 USD / night
Breakfast included · Free cancellation until 12 Nov
[Check availability]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a field is missing, leave a gap. Do not fill it with a sentence the model made up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Handle "show me more" without re-asking
&lt;/h2&gt;

&lt;p&gt;This is where most implementations leak quality. The user says "more options", the agent re-runs the search with slightly different parameters, and the second page is a different dataset with different prices.&lt;/p&gt;

&lt;p&gt;The server returns a &lt;code&gt;nextPageToken&lt;/code&gt;. Pass it back, and the next page inherits the original destination, dates, occupancy, and filters. The model does not get a chance to drift on parameters, because it is not choosing them again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"searchHotels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nextPageToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NPT_20261101_TYO_TOKEN_A1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one behaviour is the difference between a demo and something a person can use for five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Re-check the price before you promise it
&lt;/h2&gt;

&lt;p&gt;Rates move. A quote at 9 a.m. is a different number at 3 p.m., and a user who clicks a stale price loses trust in the whole product.&lt;/p&gt;

&lt;p&gt;Call &lt;code&gt;getHotelDetail&lt;/code&gt; for the chosen hotel right before rendering the confirmation, and show the timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rate checked at 14:32 UTC · total 396 USD for 2 nights
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the rate changed by more than a small threshold, say so and show the new number. Users forgive a price change. They do not forgive a silent one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Hand off, then watch what happens
&lt;/h2&gt;

&lt;p&gt;The booking URL is the end of your responsibility and the start of your instrumentation. Log the click, the hotel ID, and the quoted price. If you have an affiliate or partner arrangement, the provider's dashboard tells you what converted; the click log tells you where people hesitated.&lt;/p&gt;

&lt;p&gt;For the first week, check three numbers: clicks per session, hotels viewed before a click, and correction messages per session. If the last one is above one, the data layer is fine and the prompting or rendering is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure states you must design
&lt;/h2&gt;

&lt;p&gt;This is the part that separates a working product from a happy-path demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No results.&lt;/strong&gt; Say why: dates, budget, or filters. Offer one specific relaxation, not five.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sold out between search and detail.&lt;/strong&gt; Re-query once, then show the next closest option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing dates.&lt;/strong&gt; Ask, do not assume. "Tomorrow" from a user in another timezone is a bug report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ambiguous place.&lt;/strong&gt; "Springfield" needs a country. Ask before searching, or restrict by a country code you already have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Past dates.&lt;/strong&gt; Return an error state, not an empty list. An empty hotel list reads as "no availability", which is a different and wrong message.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this build deliberately does not do
&lt;/h2&gt;

&lt;p&gt;No payment flow. No cancellation handling. No order status screen. Those are the OAuth tier and a business onboarding conversation, and they are the right next step only after redirect mode proves people click.&lt;/p&gt;

&lt;p&gt;That is not a limitation to apologise for. It is the smallest product that tells you whether the idea works, and it ships in an afternoon instead of a quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five-minute path
&lt;/h2&gt;

&lt;p&gt;If you want the exact setup I used, the &lt;a href="https://docs.rollinggo.store/guides/redirect-mode" rel="noopener noreferrer"&gt;redirect mode guide&lt;/a&gt; covers the key, the config block, the three tools, and the response shape. Read the response example before the prose; it is the clearest description of what the product actually does.&lt;/p&gt;

&lt;p&gt;Then build the failure states first. The happy path takes an hour. The failure states are what users remember.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>travel</category>
    </item>
    <item>
      <title>Building a Travel AI Agent With Agno and Qdrant (Phidata's Successor)</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Tue, 03 Nov 2026 17:26:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/building-a-travel-ai-agent-with-agno-and-qdrant-phidatas-successor-npb</link>
      <guid>https://dev.to/iamthedev/building-a-travel-ai-agent-with-agno-and-qdrant-phidatas-successor-npb</guid>
      <description>&lt;p&gt;If you are following a Phidata tutorial from 2024, the first thing you will hit is that the package does not install the way the article says. Phidata became Agno in 2025, the imports moved, and half the travel-agent examples floating around still use the old names.&lt;/p&gt;

&lt;p&gt;This is the stack I actually run in 2026: Agno for the agent loop, Qdrant for retrieval, and an MCP server for live inventory. The parts that matter are the boundaries between them, not the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each piece is for
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tool · What it owns&lt;/th&gt;
&lt;th&gt;What it must not own&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent loop&lt;/td&gt;
&lt;td&gt;Agno · Model calls, tool dispatch, session state&lt;/td&gt;
&lt;td&gt;Business data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval&lt;/td&gt;
&lt;td&gt;Qdrant · Destination notes, policies, embeddings&lt;/td&gt;
&lt;td&gt;Live prices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;MCP server · Search, rates, room detail&lt;/td&gt;
&lt;td&gt;Your prompt logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;Your app · Rendering, auth, confirmation&lt;/td&gt;
&lt;td&gt;Model behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mistake I made twice: putting live prices in the vector store. Embeddings are snapshots. A price embedded last Tuesday is a wrong price with confident retrieval. Prices belong in a tool call, not in a document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the vector side
&lt;/h2&gt;

&lt;p&gt;Qdrant holds the parts of travel that change slowly: neighborhood guides, cancellation policy language, visa notes, accessibility details, your own post-trip writeups. Those are the facts a model cannot know and should not guess.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.vectordb.qdrant&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Qdrant&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.knowledge.embedder.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbedder&lt;/span&gt;

&lt;span class="n"&gt;vector_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Qdrant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;travel-notes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:6333&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIEmbedder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;&lt;span class="sh"&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;Chunking is where retrieval quality is won. I split by section, not by token count, and I keep the heading in every chunk. A 400-token chunk that starts mid-sentence retrieves badly, and no reranker fixes a chunk with no subject.&lt;/p&gt;

&lt;p&gt;Two metadata fields carry most of the value: &lt;code&gt;city&lt;/code&gt; and &lt;code&gt;doc_type&lt;/code&gt; (&lt;code&gt;guide&lt;/code&gt;, &lt;code&gt;policy&lt;/code&gt;, &lt;code&gt;trip_report&lt;/code&gt;). Filtering on those before vector search cut irrelevant retrievals by roughly a third in my test set. Qdrant's payload filters are cheap; use them before you reach for a bigger embedding model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChat&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;OpenAIChat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;search_hotels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hotel_detail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_notes&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer with facts from tools. Never invent a hotel name or price.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If a required field is missing, ask one question and stop.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quote prices with currency and the date they were checked.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;show_tool_calls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;Three instruction lines, and the third one does more work than it looks like. "Quote the date they were checked" makes stale data visible, which is the failure users forgive and silent staleness is the one they do not.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;show_tool_calls=True&lt;/code&gt; is not for the user. It is for me, during development, so I can see which tool produced a number before I trust the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary that decides quality
&lt;/h2&gt;

&lt;p&gt;Agno calls tools; the tools decide truth. My travel tools come from an MCP server rather than a hand-written client, for one reason: the schema and the error shapes stay on the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the agent three capabilities: &lt;code&gt;searchHotels&lt;/code&gt; for a destination, date range, star rating, and tag filters; &lt;code&gt;getHotelDetail&lt;/code&gt; for room types, rates, and cancellation rules; &lt;code&gt;getHotelSearchTags&lt;/code&gt; for the vocabulary of filters, so it stops inventing tags like "rooftop pool".&lt;/p&gt;

&lt;p&gt;The supply side is 2M+ properties across 200+ countries with 110,000 direct contracts, and the vendor reports 2,000+ developers and agents connected. I care about the part I can check: the response contains a hotel ID, a currency, a nightly rate, and a booking URL, and those four fields agree with each other.&lt;/p&gt;

&lt;p&gt;What I gave up by using a hosted server instead of my own adapter: control over retries, and the ability to cache aggressively. What I got: a schema I did not have to version, and error messages a model can act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieval and tools have to disagree safely
&lt;/h2&gt;

&lt;p&gt;The subtle bug in this stack: Qdrant says "the hotel is a 10-minute walk from the station" and the tool says the hotel is closed. Two sources, one contradiction, and the model picks whichever reads better.&lt;/p&gt;

&lt;p&gt;I handle it with precedence in the prompt and a check in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;merge_facts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note_hit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;facts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;note_hit&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotel_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;hotel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotelId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;walk_note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only attach prose from the vector store when it is keyed to the same hotel ID that came back from the tool. Fuzzy matching by name is how you get a lovely paragraph about a different property.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measure
&lt;/h2&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;Target&lt;/th&gt;
&lt;th&gt;Last run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Answers with a real hotel ID&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price within 10% of live rate&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval chunks judged relevant&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sessions needing a correction&lt;/td&gt;
&lt;td&gt;under 0.5&lt;/td&gt;
&lt;td&gt;0.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median response&lt;/td&gt;
&lt;td&gt;under 6s&lt;/td&gt;
&lt;td&gt;5.2s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The retrieval number is judged by hand on 30 queries per run, which is tedious and the only way I have found to catch chunking regressions before users do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this stack is weak
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Embedding drift.&lt;/strong&gt; Change the embedding model and you reindex everything. Pin the model name in config, and version the collection name so a reindex can run beside the old one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qdrant is another service.&lt;/strong&gt; For a weekend project, run it in Docker; for anything real, budget for backups and a collection schema you will not regret.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework churn.&lt;/strong&gt; Agno has moved fast since the rename. Pin versions, and keep your tools as plain functions so a framework change costs you the wrapper, not the logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prices are still moving objects.&lt;/strong&gt; Everything in the inventory layer is a snapshot of a moment. The agent should say when.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build order I would repeat
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Qdrant collection with 30 hand-written notes and &lt;code&gt;city&lt;/code&gt; metadata&lt;/li&gt;
&lt;li&gt;Retrieval-only prototype, no model, just check that the right chunk comes back&lt;/li&gt;
&lt;li&gt;Agent with two tools and a hard step limit&lt;/li&gt;
&lt;li&gt;Add inventory tools through MCP&lt;/li&gt;
&lt;li&gt;Eval 20 cases, then a UI&lt;/li&gt;
&lt;li&gt;Booking handoff last, because it is the part that needs the most care&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That order is not glamorous, and it is the reason this version survived contact with users when the previous one did not. The tool schemas, install steps, and response examples are in the &lt;a href="https://github.com/RollingGo-AI/RollingGo-Hotel-MCP-Global" rel="noopener noreferrer"&gt;RollingGo Hotel MCP repository&lt;/a&gt; if you want to skip the adapter work and start at step three.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>travel</category>
    </item>
    <item>
      <title>20 MCP Servers Worth Wiring Into Claude, Codex, and Gemini</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Tue, 03 Nov 2026 04:14:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/20-mcp-servers-worth-wiring-into-claude-codex-and-gemini-3jd</link>
      <guid>https://dev.to/iamthedev/20-mcp-servers-worth-wiring-into-claude-codex-and-gemini-3jd</guid>
      <description>&lt;p&gt;Most "32 MCP servers" lists are padding. The same six get repeated, three are abandoned, and two do not install.&lt;/p&gt;

&lt;p&gt;I checked 40 repositories for this list. Twenty survived: they have a named maintainer, recent commits, and for most of them, at least one independent roundup that recommends the same server. Stars are from October 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Maintainer · Stars&lt;/th&gt;
&lt;th&gt;Also named by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Context7&lt;/td&gt;
&lt;td&gt;Upstash · 63,751&lt;/td&gt;
&lt;td&gt;Firecrawl, Nimbalyst, mac.install.guide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;Microsoft · 38,686&lt;/td&gt;
&lt;td&gt;Firecrawl, Totalum, Composio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chrome DevTools&lt;/td&gt;
&lt;td&gt;Google · 54,239&lt;/td&gt;
&lt;td&gt;Firecrawl, AY Automate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;GitHub · 33,993&lt;/td&gt;
&lt;td&gt;Firecrawl, Nimbalyst, AY Automate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serena&lt;/td&gt;
&lt;td&gt;Oraios · 30,750&lt;/td&gt;
&lt;td&gt;Nimbalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Figma&lt;/td&gt;
&lt;td&gt;Framelink · 16,077&lt;/td&gt;
&lt;td&gt;Firecrawl, AY Automate, Totalum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;Firecrawl · 7,698&lt;/td&gt;
&lt;td&gt;Firecrawl, Composio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Magic (21st.dev)&lt;/td&gt;
&lt;td&gt;21st.dev · 6,272&lt;/td&gt;
&lt;td&gt;21st.dev docs, community lists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exa&lt;/td&gt;
&lt;td&gt;Exa Labs · 5,177&lt;/td&gt;
&lt;td&gt;Nimbalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare&lt;/td&gt;
&lt;td&gt;Cloudflare · 4,420&lt;/td&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;Grafana Labs · 3,611&lt;/td&gt;
&lt;td&gt;no roundup found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;crystaldba · 3,429&lt;/td&gt;
&lt;td&gt;Nimbalyst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supabase&lt;/td&gt;
&lt;td&gt;Supabase community · 2,976&lt;/td&gt;
&lt;td&gt;Firecrawl, AY Automate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tavily&lt;/td&gt;
&lt;td&gt;Tavily · 2,461&lt;/td&gt;
&lt;td&gt;Composio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Stripe · 1,975&lt;/td&gt;
&lt;td&gt;Firecrawl, AY Automate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;HashiCorp · 1,554&lt;/td&gt;
&lt;td&gt;no roundup found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qdrant&lt;/td&gt;
&lt;td&gt;Qdrant · 1,556&lt;/td&gt;
&lt;td&gt;no roundup found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MongoDB&lt;/td&gt;
&lt;td&gt;MongoDB · 1,150&lt;/td&gt;
&lt;td&gt;no roundup found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;td&gt;Sentry · 948&lt;/td&gt;
&lt;td&gt;AY Automate, GET Claude Skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RollingGo Hotel MCP&lt;/td&gt;
&lt;td&gt;RollingGo · 86&lt;/td&gt;
&lt;td&gt;no roundup found&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five entries have no third-party citation. I kept them because the vendor maintains the server and the repository is active. That is a different, weaker claim, and it should be read as one.&lt;/p&gt;

&lt;p&gt;I dropped several popular servers for concrete reasons: two were archived, one had no commit in a year, and one required a paid plan to test a single call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding and code understanding
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Context7 (Upstash)
&lt;/h3&gt;

&lt;p&gt;Version-specific docs on demand. The highest-starred server here for a reason: library APIs change faster than model training, and this is the cheapest fix.&lt;/p&gt;

&lt;p&gt;It appears in four of the roundups I checked. The failure mode is over-injection: leave it on for every prompt and you burn context on libraries the model already knows.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Playwright (Microsoft)
&lt;/h3&gt;

&lt;p&gt;Accessibility-tree browser automation. I use it to reproduce bug reports before reading code, and to walk a checkout flow after deploy.&lt;/p&gt;

&lt;p&gt;Apache-2.0, Microsoft-maintained, named in most lists. The one to skip if you already run Chrome DevTools MCP; the overlap is large.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Chrome DevTools (Google)
&lt;/h3&gt;

&lt;p&gt;Performance traces, console logs, network requests, and DOM inspection from a live browser. This is the server for "why is this page slow", where Playwright is the server for "does this flow work".&lt;/p&gt;

&lt;p&gt;54,239 stars, Google-maintained, and the newest entry in most 2026 roundups.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. GitHub (GitHub)
&lt;/h3&gt;

&lt;p&gt;Issues, PR diffs, review comments, workflow logs. Vendor-maintained, so the API surface stays current.&lt;/p&gt;

&lt;p&gt;Read-heavy in my setup. The write tools exist; I do not let them merge.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Serena (Oraios)
&lt;/h3&gt;

&lt;p&gt;Semantic code navigation over a language server: find symbol, find references, edit by symbol rather than by line. It is the most useful server nobody mentions for large repos, where grepping for a function name returns 40 hits across 12 services.&lt;/p&gt;

&lt;p&gt;30,750 stars and named by Nimbalyst. Setup is heavier than the others: it needs a language server per language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design, research, and content
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6. Figma (Framelink)
&lt;/h3&gt;

&lt;p&gt;Frames, layer names, text styles, spacing. The gap between "match the mockup" and "24px rhythm, 16px body" is most of the rework.&lt;/p&gt;

&lt;p&gt;Community-maintained, 16,077 stars, three roundup citations. Figma's own remote server is now an alternative.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Firecrawl
&lt;/h3&gt;

&lt;p&gt;Search, scrape, crawl, and map, returning clean markdown. Built for agents: large results go to files instead of flooding context.&lt;/p&gt;

&lt;p&gt;7,698 stars on the MCP repo, and the company's own docs list it as a first-class integration for Claude, Codex, and Cursor.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Magic (21st.dev)
&lt;/h3&gt;

&lt;p&gt;Generates UI components from a prompt and returns code you can paste. Narrow, but it replaces a design-to-component round trip on marketing pages.&lt;/p&gt;

&lt;p&gt;6,272 stars. Community lists cite it often; the vendor's own catalog is the primary reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data and infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  9. Exa (Exa Labs)
&lt;/h3&gt;

&lt;p&gt;Web search that returns extracted page content rather than links. Better signal per token than a search API that gives you ten URLs.&lt;/p&gt;

&lt;p&gt;5,177 stars, Nimbalyst's fifth pick for Codex.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Cloudflare
&lt;/h3&gt;

&lt;p&gt;Workers, Pages, DNS, R2, D1. Useful when the agent needs to check a deploy or a DNS record without a dashboard tab.&lt;/p&gt;

&lt;p&gt;4,420 stars, vendor-maintained, listed in Firecrawl's roundup and in OpenAI's Codex plugin directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Grafana
&lt;/h3&gt;

&lt;p&gt;Dashboards, alerts, and Prometheus queries. The server for "what changed at 14:02" during an incident.&lt;/p&gt;

&lt;p&gt;3,611 stars, vendor-maintained. No roundup citation, which is odd given how often teams run Grafana.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Postgres (crystaldba)
&lt;/h3&gt;

&lt;p&gt;Read-only SQL against a staging database, with a schema explorer that keeps the model from guessing column names.&lt;/p&gt;

&lt;p&gt;3,429 stars, MIT, community-maintained. Supabase's server (#13) is the better choice if your database lives there.&lt;/p&gt;

&lt;h3&gt;
  
  
  13. Supabase
&lt;/h3&gt;

&lt;p&gt;Project management, SQL, migrations, and logs for Supabase projects. The OAuth flow makes it the least painful hosted database server to set up.&lt;/p&gt;

&lt;p&gt;2,976 stars, officially supported by Supabase, cited by Firecrawl and AY Automate.&lt;/p&gt;

&lt;h3&gt;
  
  
  14. Tavily
&lt;/h3&gt;

&lt;p&gt;Search with an LLM-shaped response: short answers plus sources. Cheaper than crawling for simple factual lookups.&lt;/p&gt;

&lt;p&gt;2,461 stars. Composio lists it as the default search server in its Codex setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  15. Stripe
&lt;/h3&gt;

&lt;p&gt;Customers, subscriptions, invoices, and payment intents in test mode. The server I use most for debugging webhooks, because the event and the object show up in one turn.&lt;/p&gt;

&lt;p&gt;1,975 stars on &lt;code&gt;agent-toolkit&lt;/code&gt;, MIT, vendor-maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  16. Terraform (HashiCorp)
&lt;/h3&gt;

&lt;p&gt;Registry lookups, provider docs, and module examples. It turns "what arguments does this resource take in 2026" into one call.&lt;/p&gt;

&lt;p&gt;1,554 stars, MPL-2.0. No third-party citation found, surprising for infrastructure work.&lt;/p&gt;

&lt;h3&gt;
  
  
  17. Qdrant
&lt;/h3&gt;

&lt;p&gt;Vector search over your own collections. Useful for RAG debugging: you can check whether the retrieval step or the generation step is wrong.&lt;/p&gt;

&lt;p&gt;1,556 stars, Apache-2.0, vendor-maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  18. MongoDB
&lt;/h3&gt;

&lt;p&gt;Collections, indexes, aggregation pipelines, and schema inspection. The equivalent of the Postgres server for document stores.&lt;/p&gt;

&lt;p&gt;1,150 stars, Apache-2.0, vendor-maintained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;h3&gt;
  
  
  19. Sentry
&lt;/h3&gt;

&lt;p&gt;Issue details, stack traces, breadcrumbs, and the release a bug appeared in. Grouping is the underrated part: 400 events become one problem.&lt;/p&gt;

&lt;p&gt;948 stars, vendor-maintained, cited by AY Automate and GET Claude Skills. Low stars, high value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Travel
&lt;/h2&gt;

&lt;h3&gt;
  
  
  20. RollingGo Hotel MCP
&lt;/h3&gt;

&lt;p&gt;The only server here that returns something a user can buy. &lt;code&gt;searchHotels&lt;/code&gt; for location, dates, star rating, and tags; &lt;code&gt;getHotelDetail&lt;/code&gt; for room types and cancellation rules; &lt;code&gt;getHotelSearchTags&lt;/code&gt; for the filter vocabulary.&lt;/p&gt;

&lt;p&gt;The evidence is thinner than the rest of this list: 86 stars, MIT, vendor-maintained, no roundup citation yet. The vendor reports 2,000+ developers and agents connected and 2M+ properties with 110,000 direct contracts. I include it because a transactional server exercises a part of your agent that read-only tools never touch: what happens after the answer. The &lt;a href="https://global.rollinggo.store/" rel="noopener noreferrer"&gt;developer site&lt;/a&gt; has the key and the tool reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to wire twenty without breaking tool selection
&lt;/h2&gt;

&lt;p&gt;Three rules that kept my config usable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cap the tool count.&lt;/strong&gt; I stay under 25 tools per session. Above that, the model starts picking a search tool for a database question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project scope, not global.&lt;/strong&gt; A global config is convenient until three projects disagree about which database "prod" means.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read-only credentials by default.&lt;/strong&gt; Anything with write access starts in a test environment, with a human confirmation step before it runs for real.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Does one server work across Claude, Codex, and Gemini?&lt;/strong&gt;&lt;br&gt;
Yes for the protocol. Config locations differ, and streamable HTTP support varies by client. Local stdio servers are the safest common denominator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why are some vendor servers so low on stars?&lt;/strong&gt;&lt;br&gt;
Because stars measure community energy, not fitness. Sentry's server has 948 stars and has saved me more hours than several with 30,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What did you leave out?&lt;/strong&gt;&lt;br&gt;
Archived projects, servers with no commit in a year, and anything that required a paid plan before the first successful call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which one first?&lt;/strong&gt;&lt;br&gt;
Context7 if you work with fast-moving libraries, GitHub if you live in PRs, and the hotel one if your product needs to finish a transaction rather than describe one.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How I Build AI Projects From Scratch (Six Decisions, One Rule)</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Sun, 01 Nov 2026 17:41:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/how-i-build-ai-projects-from-scratch-six-decisions-one-rule-10k8</link>
      <guid>https://dev.to/iamthedev/how-i-build-ai-projects-from-scratch-six-decisions-one-rule-10k8</guid>
      <description>&lt;p&gt;I have shipped four AI products in three years, two of which earned money and two of which I shut down. The difference was never the model.&lt;/p&gt;

&lt;p&gt;The pattern I trust now fits in one sentence: pick a job with a checkable outcome, write the eval before the prompt, and spend your effort on the data layer instead of the intelligence layer.&lt;/p&gt;

&lt;p&gt;Here is the process, in the order I actually do it, with the numbers from the last project.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start from a checkable outcome
&lt;/h2&gt;

&lt;p&gt;Bad framing: "an AI assistant for travel." There is no way to know if it worked.&lt;/p&gt;

&lt;p&gt;Good framing: "given a city and a date range, return three hotels that exist, with prices within 10% of the live rate, in under six seconds."&lt;/p&gt;

&lt;p&gt;The second version tells me what to build, what to measure, and when to quit. It also rules out a lot of features I would otherwise build because they demo well.&lt;/p&gt;

&lt;p&gt;I write the outcome sentence before I write any code, and I keep it at the top of the README. When the project drifts, which it always does, that sentence is the thing I compare against.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Write the eval before the prompt
&lt;/h2&gt;

&lt;p&gt;This is the step people skip, and it is the one that decides whether you are doing engineering or vibes.&lt;/p&gt;

&lt;p&gt;Before the first prompt, I collect 20 real cases and write down the pass condition for each. Not "looks good". Something a script can check or a stranger can verify in five seconds.&lt;/p&gt;

&lt;p&gt;For the travel project the eval had four columns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Must return · Must not&lt;/th&gt;
&lt;th&gt;Pass condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kyoto, 2 nights, 4-star&lt;/td&gt;
&lt;td&gt;3 hotels · invented names&lt;/td&gt;
&lt;td&gt;all IDs exist in fixtures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lisbon, past dates&lt;/td&gt;
&lt;td&gt;error message · a hotel list&lt;/td&gt;
&lt;td&gt;mentions the date problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokyo, budget 8,000 JPY&lt;/td&gt;
&lt;td&gt;3 hotels · 5-star results&lt;/td&gt;
&lt;td&gt;max price under budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"somewhere warm in March"&lt;/td&gt;
&lt;td&gt;clarifying question · a guess&lt;/td&gt;
&lt;td&gt;asks for a city&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty cases took two hours to write and saved me from shipping three broken versions. Every time I changed a prompt, I re-ran them. Pass rate went from 11/20 to 18/20 in the first week, mostly from fixing the data layer rather than the wording.&lt;/p&gt;

&lt;p&gt;The eval is also the only honest answer to "is it better now?". Without it, every change feels like an improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Build the smallest possible loop
&lt;/h2&gt;

&lt;p&gt;My agents are a while loop: call the model, run a tool, store the result, repeat until a final answer or a step budget runs out. No planner, no graph, no memory layer.&lt;/p&gt;

&lt;p&gt;The loop is around 300 lines including retries and logging. I have written about the mechanics separately, so here is only the design constraint: every step must be reconstructible from the log. If I cannot replay a session and see the exact tool arguments, the loop is too clever.&lt;/p&gt;

&lt;p&gt;Step budgets and timeouts are not polish. They are what turns a demo into something that can run unattended. Mine are eight steps and 45 seconds, and they are the first two constants in the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Buy the data layer, build the loop
&lt;/h2&gt;

&lt;p&gt;The single highest-value decision in every project was to stop writing integrations.&lt;/p&gt;

&lt;p&gt;My first travel agent had 1,900 lines of adapters: auth wrappers, response mappers, retry logic, and a fuzzy name matcher that reconciled three sources. It broke whenever a supplier changed a field. The second version replaced that layer with an MCP server and kept the loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this specific one, since "use an MCP server" is generic advice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It owns the schema.&lt;/strong&gt; &lt;code&gt;searchHotels&lt;/code&gt;, &lt;code&gt;getHotelDetail&lt;/code&gt;, &lt;code&gt;getHotelSearchTags&lt;/code&gt; are defined server-side, so validation and error shapes stop being my problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The supply numbers are checkable in the response.&lt;/strong&gt; 2M+ properties across 200+ countries, 110,000 direct contracts. A fake layer cannot return a hotel ID, a live rate, and a working booking link in the same payload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is free with no call cap at the API key tier&lt;/strong&gt;, which matters when you are validating an idea and cannot forecast volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is young.&lt;/strong&gt; The repo is public and MIT licensed, and the vendor reports 2,000+ developers and agents connected. That is not the same evidence base as a 60,000-star project, and I would rather say so than dress it up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The adapter deletion took the codebase from 1,900 lines to 240. More importantly, it changed what the agent could claim: real prices instead of plausible ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make wrongness visible
&lt;/h2&gt;

&lt;p&gt;Every AI product I built had a wrongness rate. The ones that survived were the ones where users could see it and recover.&lt;/p&gt;

&lt;p&gt;Three mechanisms, in increasing order of usefulness:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cite the source of a fact.&lt;/strong&gt; A hotel ID, a price timestamp, a document name. If the model cannot name where a number came from, the number should not be on screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Show the failure state.&lt;/strong&gt; "No hotels match those dates" is a feature. Empty space is a bug report waiting to happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log the correction.&lt;/strong&gt; When a user says "that is wrong", write down what they corrected and which tool produced it. After 40 users I had a list of six recurring failures, and four of them were data problems, not prompt problems.&lt;/p&gt;

&lt;p&gt;The wrongness rate on the travel project went from 26% (model-only) to under 1% after the data layer swap, with four percent of prices still drifting between quote and click. I know those numbers because the eval measured them, not because it felt better.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Set a kill criterion before you fall in love
&lt;/h2&gt;

&lt;p&gt;I give every project three weeks and one metric.&lt;/p&gt;

&lt;p&gt;If the eval pass rate is under 60% after three weeks of real work, I stop. Not because the idea is bad, but because the gap is usually structural: the data does not exist, the task is not checkable, or the user does not care enough to correct the output.&lt;/p&gt;

&lt;p&gt;Two of my four projects died on that rule. One was an AI meeting summarizer where the output was fine and nobody needed it. The other was a document Q&amp;amp;A tool where the retrieval quality plateaued at 55% and every fix cost a week.&lt;/p&gt;

&lt;p&gt;Killing them early is the reason the other two got the time they needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I stopped doing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fine-tuning.&lt;/strong&gt; Every problem I had was a data or eval problem. Fine-tuning moved the failure around without removing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent frameworks.&lt;/strong&gt; I tried four. Each added a layer between me and the behavior I was debugging. The loop is 300 lines and I can print it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt libraries.&lt;/strong&gt; A prompt that works on one model version is a liability on the next. What survives a model upgrade is a clear tool schema and a checkable outcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Big launches.&lt;/strong&gt; The project that worked had 40 users for a month before anyone outside heard about it. The one that failed had a landing page and no working eval.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist I actually use
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;One sentence describing a checkable outcome&lt;/li&gt;
&lt;li&gt;20 eval cases with pass conditions&lt;/li&gt;
&lt;li&gt;A loop with a step budget and a deadline&lt;/li&gt;
&lt;li&gt;One data source you do not have to maintain&lt;/li&gt;
&lt;li&gt;A wrongness rate you can quote&lt;/li&gt;
&lt;li&gt;A date on which you will stop&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rest, UI, billing, marketing, can be added later. None of it saves a product whose facts are wrong.&lt;/p&gt;

&lt;p&gt;If you want the data-layer shortcut I used, the &lt;a href="https://github.com/RollingGo-AI/RollingGo-Hotel-MCP-Global" rel="noopener noreferrer"&gt;RollingGo Hotel MCP repository&lt;/a&gt; has the config, the tool schemas, and the response examples in one place. Read the response schema first; it tells you more about the product than the marketing page does.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>6 npm Packages You Can Delete in 2026 (Node Does It Now)</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Sun, 01 Nov 2026 03:23:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/6-npm-packages-you-can-delete-in-2026-node-does-it-now-3eko</link>
      <guid>https://dev.to/iamthedev/6-npm-packages-you-can-delete-in-2026-node-does-it-now-3eko</guid>
      <description>&lt;p&gt;Every dependency you delete is a supply chain risk you stop carrying, a lockfile conflict you stop resolving, and a &lt;code&gt;npm audit&lt;/code&gt; line that stops appearing in your CI log.&lt;/p&gt;

&lt;p&gt;Six packages in my projects became unnecessary because Node absorbed their job. Not experimental flags I read about once, the boring built-ins I now reach for by default. Versions below are the first release where the feature became usable, so check your &lt;code&gt;engines&lt;/code&gt; field before deleting anything.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Replaced by · Available since&lt;/th&gt;
&lt;th&gt;What you lose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;dotenv&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;node --env-file&lt;/code&gt; · Node 20.6&lt;/td&gt;
&lt;td&gt;Nothing for most apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;crypto.randomUUID()&lt;/code&gt; · Node 16.7&lt;/td&gt;
&lt;td&gt;v1/v3/v5 formats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;axios (simple use)&lt;/td&gt;
&lt;td&gt;global &lt;code&gt;fetch&lt;/code&gt; · Node 21 stable&lt;/td&gt;
&lt;td&gt;Interceptors, progress events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lodash.get&lt;/td&gt;
&lt;td&gt;optional chaining · Node 14&lt;/td&gt;
&lt;td&gt;Deep-path string syntax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rimraf&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fs.rm({ recursive: true })&lt;/code&gt; · Node 14.14&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jest&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;node:test&lt;/code&gt; · Node 20&lt;/td&gt;
&lt;td&gt;Snapshot tooling, ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is roughly 12 MB of &lt;code&gt;node_modules&lt;/code&gt; and four transitive dependency trees gone from a typical service. The bigger win is that &lt;code&gt;npm ci&lt;/code&gt; stops failing on a peer conflict caused by a testing library nobody on the team chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. dotenv, replaced by &lt;code&gt;--env-file&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The runtime reads the file. No import, no &lt;code&gt;dotenv/config&lt;/code&gt; in a test setup file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--env-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.env server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node 20.6 added the flag, and 22 made it stable enough for production. &lt;code&gt;process.env&lt;/code&gt; is populated before your first line runs, which also fixes the ordering bug where a config module reads &lt;code&gt;process.env&lt;/code&gt; before dotenv has loaded.&lt;/p&gt;

&lt;p&gt;Two caveats. There is no variable expansion, so &lt;code&gt;DATABASE_URL=${HOST}:5432&lt;/code&gt; stays literal. And a missing file is an error, which is what you want in CI and annoying on a fresh clone, where &lt;code&gt;--env-file-if-exists&lt;/code&gt; is the flag you actually want.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. uuid, replaced by &lt;code&gt;crypto.randomUUID()&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;randomUUID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// 36 chars, v4, RFC 4122&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node 16.7 and later, no dependency, and it is faster than the package for the common v4 case.&lt;/p&gt;

&lt;p&gt;You lose v1 (timestamp-ordered) and v5 (namespace hashing). If you need sortable IDs, &lt;code&gt;randomUUID&lt;/code&gt; is the wrong tool anyway; a ULID or a database sequence is a better answer than uuid v1.&lt;/p&gt;

&lt;p&gt;One production note: if your IDs land in a URL or a log, &lt;code&gt;randomUUID&lt;/code&gt; is fine, but do not assume it is unguessable for authorization decisions. It is random, not a secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. axios, replaced by &lt;code&gt;fetch&lt;/code&gt; for the simple cases
&lt;/h2&gt;

&lt;p&gt;Node 21 shipped a stable global &lt;code&gt;fetch&lt;/code&gt;, and 22 is what most services run now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AbortSignal.timeout&lt;/code&gt; is the part people miss. Without it, a hung upstream pins your request until the load balancer gives up. That single line replaced most of what I used axios interceptors for.&lt;/p&gt;

&lt;p&gt;What you give up: interceptors, upload progress, and automatic JSON parsing on error responses. If your codebase leans on those, keep axios. If it wraps every call in a helper that adds a timeout and throws on non-2xx, the helper is now ten lines and the dependency is optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. lodash.get, replaced by optional chaining
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node 14, no import, and type checkers understand it, which &lt;code&gt;_.get(obj, "a.b.c")&lt;/code&gt; never did.&lt;/p&gt;

&lt;p&gt;The string-path form still wins when the path is dynamic: &lt;code&gt;_.get(obj, userProvidedPath)&lt;/code&gt; cannot be expressed as &lt;code&gt;?.&lt;/code&gt;. That case is rare, and when it appears it usually deserves a validation step rather than a silent &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same logic applies to &lt;code&gt;lodash.merge&lt;/code&gt; and &lt;code&gt;lodash.cloneDeep&lt;/code&gt;. &lt;code&gt;structuredClone&lt;/code&gt; handles the clone case natively since Node 17, with the caveat that functions, symbols, and class prototypes do not survive the copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. rimraf, replaced by &lt;code&gt;fs.rm&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;rm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;rm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&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;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Node 14.14. Cross-platform, no extra binary, and &lt;code&gt;force: true&lt;/code&gt; covers the "directory does not exist" case that made &lt;code&gt;rimraf&lt;/code&gt; popular in the first place.&lt;/p&gt;

&lt;p&gt;The one place I still see rimraf in scripts is Windows path-length problems on deeply nested &lt;code&gt;node_modules&lt;/code&gt;. &lt;code&gt;fs.rm&lt;/code&gt; handles long paths on modern Node, but if you support Node 16 on Windows CI, test before deleting.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. jest, replaced by &lt;code&gt;node:test&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For a service with 40 to 200 tests, the built-in runner is enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:test&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="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rejects an empty city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rejects&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;searchHotels&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="sr"&gt;/city is required/&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;Run it with &lt;code&gt;node --test&lt;/code&gt;. Node 20 made the runner stable, and the API covers describe/it, hooks, mocking with &lt;code&gt;mock&lt;/code&gt;, and coverage with &lt;code&gt;--experimental-test-coverage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What you actually lose: snapshot testing, the Jest ecosystem (jsdom environment, dozens of matchers), and the muscle memory of your team. For a React app with component tests, keep Jest or move to Vitest. For a backend with pure functions and an HTTP client, &lt;code&gt;node:test&lt;/code&gt; plus &lt;code&gt;assert&lt;/code&gt; is enough, and your CI starts in 300ms instead of 8 seconds.&lt;/p&gt;

&lt;p&gt;Honest caveat: I moved two services and kept Jest in a third, because the third had 600 tests and a snapshot suite that would have cost a week to port. Deleting a dependency is not worth a week of churn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dependency I would not delete, but would stop writing
&lt;/h2&gt;

&lt;p&gt;There is one category where the package is not the problem. It is the pattern of writing a client library for every third-party API.&lt;/p&gt;

&lt;p&gt;I had three travel projects, and each one grew the same four files: an auth wrapper, a retry layer, a response mapper, and a pile of error strings. The hotel API changed its response shape once and all three broke on the same Tuesday.&lt;/p&gt;

&lt;p&gt;The fix was not a better SDK. It was moving the integration to an MCP server, where the schema lives on the server and my code only consumes it. The whole config is a JSON block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three tools, &lt;code&gt;searchHotels&lt;/code&gt;, &lt;code&gt;getHotelDetail&lt;/code&gt;, &lt;code&gt;getHotelSearchTags&lt;/code&gt;, replaced about 1,900 lines across those projects. The server is free with no call cap, covers 2M+ properties, and the vendor reports 2,000+ developers and agents connected. Whether you use this one or another, the principle holds: delete the client library, keep the interface. The &lt;a href="https://global.rollinggo.store/" rel="noopener noreferrer"&gt;developer site&lt;/a&gt; has the key request and the schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you delete
&lt;/h2&gt;

&lt;p&gt;Three checks that saved me from a bad afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the engines field.&lt;/strong&gt; &lt;code&gt;--env-file&lt;/code&gt; on Node 18 fails, and the error is not obvious in a container built from an older base image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grep for the edge case, not the common one.&lt;/strong&gt; Nobody uses &lt;code&gt;_.get&lt;/code&gt; with a dynamic path until the one place that does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete one at a time.&lt;/strong&gt; A six-package PR is unreviewable. Six one-line PRs take an hour and each one is trivially revertible.&lt;/p&gt;

&lt;p&gt;The goal is not a dependency-free project. It is a project where every dependency is a decision someone can defend, instead of an install that happened in 2021 and nobody has looked at since.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building an AI Agent From Scratch: 300 Lines, One Loop, No Framework</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Fri, 30 Oct 2026 16:05:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/building-an-ai-agent-from-scratch-300-lines-one-loop-no-framework-4b26</link>
      <guid>https://dev.to/iamthedev/building-an-ai-agent-from-scratch-300-lines-one-loop-no-framework-4b26</guid>
      <description>&lt;p&gt;Every agent framework I tried in 2023 solved a problem I did not have yet, and hid the problem I did have: the loop.&lt;/p&gt;

&lt;p&gt;So I built the last three agents without one. Parse, call a tool, check the result, decide again. A while loop with a step budget, a state object, and a tool registry. The first version fit in 300 lines and I could print every decision it made.&lt;/p&gt;

&lt;p&gt;This is the whole build, plus the three failure modes that made me keep the loop boring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why skip the framework
&lt;/h2&gt;

&lt;p&gt;Frameworks optimize for the demo: chains, planners, retries, memory, tracing, a graph editor. They also add a layer between you and the model's behavior at exactly the moment you need to see it.&lt;/p&gt;

&lt;p&gt;My rule now: build the loop by hand until the product works, then adopt a framework only if a specific feature pays for its weight. In practice that means I have never adopted one.&lt;/p&gt;

&lt;p&gt;The trade is real. You write retries, timeouts, and logging yourself. That is roughly 200 lines you would rather not own. You also get a stack trace that points at your code instead of inside a library.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;MAX_STEPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;DEADLINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_message&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MAX_STEPS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DEADLINE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown tool: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# bad arguments
&lt;/span&gt;            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid arguments: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool timed out after 10s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step budget exhausted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things are doing the real work here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A step budget.&lt;/strong&gt; Eight is enough for search, detail, compare, answer. Without it, a confused model will keep calling the same tool. I have watched one do it eleven times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A wall-clock deadline.&lt;/strong&gt; Steps and seconds fail differently. A slow tool with a small step count still hangs the request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool errors as data, not exceptions.&lt;/strong&gt; If a tool throws and the loop dies, the user sees a 500. If the error goes back into state, the model gets a chance to fix its arguments. That single change cut my failed sessions by more than half.&lt;/p&gt;

&lt;h2&gt;
  
  
  State is the product
&lt;/h2&gt;

&lt;p&gt;The loop is 30 lines. The state design decides whether the agent is debuggable.&lt;/p&gt;

&lt;p&gt;I keep one dict with three keys: the message list, a list of tool results, and a small scratchpad for facts the agent has confirmed. Every tool result is stored as structured data, never as prose pasted into the prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;facts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotel_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotelList&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotelId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line matters more than it looks. When the model later says "the hotel we discussed", it is referring to a value that exists, not to a paragraph it half-remembers. Most of the hallucination I saw in early versions came from facts living only in the transcript.&lt;/p&gt;

&lt;p&gt;At the end of each turn I render the facts into a compact block, four or five lines, rather than replaying the whole conversation. Context stays small and the model stays on topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools: schemas first, code second
&lt;/h2&gt;

&lt;p&gt;A tool is a Python function plus a JSON Schema. The schema is the interface the model actually sees, so I write it before the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_hotels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;place&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_hotels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find bookable hotels near a place. Returns hotel id, name, nightly price, currency.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;place&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;City, station, or landmark&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YYYY-MM-DD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nights&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minimum&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maximum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;place&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rules I follow and break at my peril.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name the output, not the action.&lt;/strong&gt; "Returns hotel id, name, nightly price" tells the model what it will get. "Searches hotels" does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constrain the obvious mistakes.&lt;/strong&gt; &lt;code&gt;minimum&lt;/code&gt;, &lt;code&gt;maximum&lt;/code&gt;, enums, and date formats in the description remove a whole class of retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure one: the loop that would not stop
&lt;/h2&gt;

&lt;p&gt;First real user, first bug: the agent searched the same city four times with different spacing in the date string, never satisfied with the result.&lt;/p&gt;

&lt;p&gt;The fix was not a better prompt. It was deduplication in state: hash the arguments, refuse a second identical call, and tell the model it already has that answer. One line, and the behavior stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure two: the confident empty answer
&lt;/h2&gt;

&lt;p&gt;A tool returned &lt;code&gt;{"hotels": []}&lt;/code&gt; because the dates were in the past. The model summarized it as "no availability that weekend" and moved on.&lt;/p&gt;

&lt;p&gt;Now every tool returns either data or an explicit reason. Empty list plus a reason becomes an error state the loop can act on. A model cannot distinguish "no results" from "I broke" unless you tell it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure three: context bloat
&lt;/h2&gt;

&lt;p&gt;Tool results are large. A hotel search with five properties and full amenity lists is 4,000 tokens. Three calls and the conversation is mostly JSON.&lt;/p&gt;

&lt;p&gt;The fix is boring: project the result before it goes into state. Keep the eight fields the agent needs, drop images, descriptions, and nested policy objects. If the user asks for detail, call the detail tool then.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to stop writing clients and use MCP
&lt;/h2&gt;

&lt;p&gt;Halfway through my second agent I noticed I was writing the same adapter for the third time: auth header, retries, response mapping, error strings. That is the point where an MCP server earns its place, because the server owns the schema and you own the loop.&lt;/p&gt;

&lt;p&gt;The swap for my travel project was a config block. This is the whole integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three tools come with it: &lt;code&gt;searchHotels&lt;/code&gt;, &lt;code&gt;getHotelDetail&lt;/code&gt;, &lt;code&gt;getHotelSearchTags&lt;/code&gt;. My loop did not change. The tool registry got smaller, because the server validates arguments and returns typed errors. The 1,900 lines of adapter code I had written across three projects became 240 lines of formatting and error handling.&lt;/p&gt;

&lt;p&gt;For a from-scratch agent, that is the right division of labor: your 300 lines stay yours, and the data layer stops being your problem. The &lt;a href="https://docs.rollinggo.store/guides/redirect-mode" rel="noopener noreferrer"&gt;5-minute redirect mode guide&lt;/a&gt; covers the setup and explains how the booking handoff works without you touching payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing a loop
&lt;/h2&gt;

&lt;p&gt;Unit tests do not catch agent bugs. Replay does.&lt;/p&gt;

&lt;p&gt;I log every step: model action, tool name, arguments, result hash, latency. Then a test suite replays 40 real traces against the current code and asserts on outcomes, not wording. "Must contain a hotel ID that exists in the fixture", "must not call the same tool twice with identical arguments".&lt;/p&gt;

&lt;p&gt;Forty traces caught every regression I shipped in the last six months. It is the cheapest quality investment in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would add next
&lt;/h2&gt;

&lt;p&gt;Two things, in this order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A confirmation gate for write tools.&lt;/strong&gt; Anything that books, pays, or deletes should require an explicit user step, not a model decision. My loop has no writes today, which is why it is safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured logging to a file, not stdout.&lt;/strong&gt; When a session goes wrong at 2 a.m., you want the trace, not a memory of it.&lt;/p&gt;

&lt;p&gt;Everything else, memory, planning, multi-agent orchestration, can wait until a user asks for something the loop cannot do. Most of the time, the loop can do it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Gave an AI Travel Agent 30 Days. The Hotel Layer Almost Killed It</title>
      <dc:creator>iamTheDev</dc:creator>
      <pubDate>Fri, 30 Oct 2026 03:37:00 +0000</pubDate>
      <link>https://dev.to/iamthedev/i-gave-an-ai-travel-agent-30-days-the-hotel-layer-almost-killed-it-5a3i</link>
      <guid>https://dev.to/iamthedev/i-gave-an-ai-travel-agent-30-days-the-hotel-layer-almost-killed-it-5a3i</guid>
      <description>&lt;p&gt;Thirty evenings, one laptop, no budget for APIs, one goal: an AI trip planner that returns hotels a person can actually book. Not a demo. Something 40 strangers could use without me standing behind them.&lt;/p&gt;

&lt;p&gt;It shipped on day 30. It also spent two weeks producing confident, well-formatted lies about hotels, and the fix was not a better prompt.&lt;/p&gt;

&lt;p&gt;This is the log, with the numbers I measured on the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that shaped everything
&lt;/h2&gt;

&lt;p&gt;I had three rules for myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No framework.&lt;/strong&gt; I wanted to know which part failed when something broke.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No invented data.&lt;/strong&gt; If the agent could not verify a hotel, it had to say so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No more than six tools&lt;/strong&gt; in the first version, because every tool is a chance for the model to pick wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first rule saved the project. The third rule was too optimistic. The second rule is the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Days 1 to 7: build the loop before the intelligence
&lt;/h2&gt;

&lt;p&gt;The first version was a deterministic loop: parse intent, call a tool, check the result, decide whether to call another tool, then answer. No autonomous planning. No "agent decides its own steps". A while loop with a step budget and a state object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hotels&lt;/span&gt;&lt;span class="sh"&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;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ugly, and it worked. On day 3 it could take "four nights in Lisbon in March, not too expensive, near the river" and return a plan with a neighborhood, three activities, and a hotel list. The hotel list was the problem, and I did not know it yet.&lt;/p&gt;

&lt;p&gt;Two things I got right early: a hard step budget, and a rule that every tool result gets written into state as structured data rather than pasted text. The second one made the failure visible later, because I could count what actually came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Days 8 to 14: the wall
&lt;/h2&gt;

&lt;p&gt;On day 11 a beta user messaged me a screenshot. The planner had recommended a hotel with a nice description, a plausible price of 148 EUR, and a booking link that led to a 404. The hotel had closed in 2023.&lt;/p&gt;

&lt;p&gt;I pulled 120 hotel recommendations from the previous week and checked them by hand. That afternoon was the worst part of the month.&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;Model-only version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hotel exists and is open&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price within 10% of the live rate&lt;/td&gt;
&lt;td&gt;39%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct neighborhood&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancellation policy stated correctly&lt;/td&gt;
&lt;td&gt;31%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty-six percent of recommendations pointed at hotels that were closed, renamed, or never existed. Only about a third of the prices survived a spot check. The descriptions were excellent, which made it worse. A user trusts fluent text.&lt;/p&gt;

&lt;p&gt;I want to be fair to the model here. It was not malfunctioning. It was answering a question it had no data for. Hotel inventory changes daily. No amount of training data holds that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Days 15 to 19: three fixes that did not work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fix one: stronger instructions.&lt;/strong&gt; "Only recommend hotels you are certain exist. Never invent prices." The invented hotel rate fell from 26% to 22%. The model got better at sounding certain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix two: ask it to cite sources.&lt;/strong&gt; It produced citations. Some pointed to pages that did not mention the hotel. Asking a model to justify a guess produces justification, not truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix three: a bigger model.&lt;/strong&gt; Better prose, same failure rate. This one cost me a day and taught me the most. The problem was architectural, not intellectual.&lt;/p&gt;

&lt;p&gt;The pattern in all three attempts: I was trying to make the model more careful about facts it did not have. The only real fix was to give it facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Days 20 to 24: one afternoon to replace the guess layer
&lt;/h2&gt;

&lt;p&gt;I had been putting off a real inventory integration because my memory of travel APIs was three weeks of auth debugging, XML schemas, and adapter code for every supplier. That memory was from 2022.&lt;/p&gt;

&lt;p&gt;The MCP version took an afternoon. RollingGo Hotel MCP speaks streamable HTTP at a single endpoint, the vendor maintains it, and the API key tier is free with no call cap, so I could test without budgeting.&lt;/p&gt;

&lt;p&gt;I checked the numbers before trusting it with forty strangers: MIT licensed server, over two million properties across 200+ countries, 500+ suppliers, and 110,000 direct contracts. The part that mattered for my timeline was the redirect mode, which returns a &lt;code&gt;bookingUrl&lt;/code&gt; per hotel and pushes the actual transaction to RollingGo. No payment gateway, no cancellation logic, no PCI conversation. I would have spent the whole week on that alone.&lt;/p&gt;

&lt;p&gt;Two provenance checks, because I ask this of every tool I recommend. First, the server is maintained by the company that owns the supply side, not a reseller wrapping someone else's API, and the inventory numbers in the marketing copy matched what came back in a real response. Second, it is young: 86 GitHub stars, no third-party roundup, no benchmark. That is the opposite of Context7's 63,000 stars. If you need a pick with a crowd behind it, this is not that pick. What it has is one job that none of the popular servers do, and the 30 days below are the only evidence I can offer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rollinggo-hotel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.rollinggo.ai/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three tools replaced my fake data layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;searchHotels&lt;/code&gt; for a location, date range, star rating, and tag filters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getHotelDetail&lt;/code&gt; for live room types, nightly rates, and cancellation rules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getHotelSearchTags&lt;/code&gt; for the vocabulary of filters, so the agent stops inventing "rooftop pool" as a tag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent's tool definition got shorter, not longer, because the server owns the parameters. My prompt went from 1,100 words of formatting instructions to 340.&lt;/p&gt;

&lt;p&gt;Then the interesting part. I deleted 1,900 lines of adapter code: my own hotel cache, my price formatter, my retry wrapper, and a fuzzy name matcher I had written to reconcile three fake data sources. The replacement is 240 lines, most of it error handling and a currency formatter for display.&lt;/p&gt;

&lt;h2&gt;
  
  
  Days 25 to 30: forty users, and what they noticed
&lt;/h2&gt;

&lt;p&gt;I shipped to 40 beta users with a two-question feedback form. Nobody mentioned the architecture. They mentioned speed and trust.&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;Week 2 (model only)&lt;/th&gt;
&lt;th&gt;Week 4 (with live inventory)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Recommendations pointing to a real, open hotel&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price within 10% of live rate&lt;/td&gt;
&lt;td&gt;39%&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User corrections per session&lt;/td&gt;
&lt;td&gt;2.4&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median first response&lt;/td&gt;
&lt;td&gt;7.8s&lt;/td&gt;
&lt;td&gt;5.1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adapter code&lt;/td&gt;
&lt;td&gt;1,900 lines&lt;/td&gt;
&lt;td&gt;240 lines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 96% is honest: four percent of checked prices still drifted, because a rate quoted during the conversation can change before the user clicks. I added a re-check before rendering the final number and a note that prices update at booking time.&lt;/p&gt;

&lt;p&gt;The 0.3 corrections per session is the number I care about. In week two, users were constantly telling the agent "that hotel doesn't exist" or "that price is wrong". By week four the conversation was about the trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still breaks
&lt;/h2&gt;

&lt;p&gt;Three things, in order of how often they bite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate drift.&lt;/strong&gt; Live data at 9 a.m. is not live at 3 p.m. Any price shown for more than a few minutes needs a refresh step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Currency and rounding.&lt;/strong&gt; The API returns a currency and a nightly price. Tax handling varies by property and country, and a user comparing two hotels will notice. I now label the number as a nightly rate before taxes and keep that label in every view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Booking completion.&lt;/strong&gt; The API key tier covers search and detail. The booking link hands off to the property page, which is what redirect mode is designed for: the agent recommends, RollingGo completes the transaction. That was the right trade for a 30-day experiment. The OAuth tier adds order lookup and price confirmation and needs business onboarding, which is the path for a product that wants to show order status inside the conversation rather than in a browser tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with the data layer.&lt;/strong&gt; I spent two weeks learning that prompts cannot substitute for inventory. I could have learned it on day two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the audit before the UI.&lt;/strong&gt; A spreadsheet of 120 checked recommendations told me more than any dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the deterministic loop.&lt;/strong&gt; Every time I tried to let the agent plan freely, the step count doubled and the answer got worse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the failure states first.&lt;/strong&gt; "No hotels match those dates" is a feature. It took me until week four to render it well.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Day 30
&lt;/h2&gt;

&lt;p&gt;The planner works. It handles a city, a date range, a budget, and a walkability preference, and it returns hotels with real rates. Forty people used it. Eleven came back a second time, which for a side project with no marketing is the number I keep looking at.&lt;/p&gt;

&lt;p&gt;The lesson is not "use MCP". It is narrower and more useful: an AI product is only as honest as its least verifiable data source. Once hotels came from inventory instead of memory, everything downstream got simpler. Fewer retries, shorter prompts, fewer apologies.&lt;/p&gt;

&lt;p&gt;If you want to test the same swap on a weekend, the setup is a config block and a key. The integration path I used is &lt;a href="https://docs.rollinggo.store/guides/redirect-mode" rel="noopener noreferrer"&gt;RollingGo's redirect mode guide&lt;/a&gt;. Start with the audit spreadsheet, though. It will tell you more about your agent than any benchmark.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>travel</category>
    </item>
  </channel>
</rss>
