<?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: Ahmed Gamal Abdou Ahmed</title>
    <description>The latest articles on DEV Community by Ahmed Gamal Abdou Ahmed (@ahmed_gamalabdouahmed_f).</description>
    <link>https://dev.to/ahmed_gamalabdouahmed_f</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%2F4014138%2F54fc4b04-7252-488e-9ac3-e1ac3bf4e7e1.png</url>
      <title>DEV Community: Ahmed Gamal Abdou Ahmed</title>
      <link>https://dev.to/ahmed_gamalabdouahmed_f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmed_gamalabdouahmed_f"/>
    <language>en</language>
    <item>
      <title>How we built a booking email engine that never loses a guest — Cloudflare Workflows, Durable Objects and D1</title>
      <dc:creator>Ahmed Gamal Abdou Ahmed</dc:creator>
      <pubDate>Sat, 17 Oct 2026 12:01:53 +0000</pubDate>
      <link>https://dev.to/ahmed_gamalabdouahmed_f/how-we-built-a-booking-email-engine-that-never-loses-a-guest-cloudflare-workflows-durable-28lk</link>
      <guid>https://dev.to/ahmed_gamalabdouahmed_f/how-we-built-a-booking-email-engine-that-never-loses-a-guest-cloudflare-workflows-durable-28lk</guid>
      <description>&lt;p&gt;At MAQAMI &lt;a href="https://maqami.co" rel="noopener noreferrer"&gt;https://maqami.co&lt;/a&gt; we sell hotels and flights worldwide — over 3 million properties plus flights, with instant confirmation and 24/7 support. A travel booking is not one moment; it is a story that lasts weeks: checkout, payment, confirmation, the hotel's own confirmation number, a schedule change, the day before check-in, the day after check-out.&lt;/p&gt;

&lt;p&gt;For a long time each of those moments was a separate, fragile automation. Some fired twice, some never fired, and some sent the guest a cheerful "see you soon" after the airline had changed the flight.&lt;/p&gt;

&lt;p&gt;So we rebuilt it as one engine on Cloudflare Workers. This post covers the architecture, the code patterns that made it reliable, and five bugs that taught us more than the happy path did.&lt;/p&gt;

&lt;p&gt;TL;DR&lt;br&gt;
Every webhook event becomes a durable Cloudflare Workflow. The event ID is the workflow ID, so duplicates are ignored for free.&lt;br&gt;
Every customer email is sent "once per booking and kind", guarded by a key, because webhooks are at-least-once.&lt;br&gt;
Long waits ("3 days before check-in") are workflow sleeps, chunked to stay under the 365-day limit.&lt;br&gt;
All client email goes through one Durable Object outbox: one email every 150–160 seconds, ranked urgent → booking → service → marketing, with automatic failover to SMTP when the main provider's daily quota runs out.&lt;br&gt;
Card declines never reach your webhook. We check 20 minutes after the last checkout attempt instead, and hold anything that looks like card testing for a human.&lt;br&gt;
The shape of the system&lt;br&gt;
supplier webhooks ──► Worker /webhook ──► Workflow (one per event)&lt;br&gt;
                                              │&lt;br&gt;
            ┌─────────────────────────────────┼──────────────────────────────┐&lt;br&gt;
            ▼                                 ▼                              ▼&lt;br&gt;
   D1 (contacts, bookings,          Outbox (Durable Object)          Owner alerts&lt;br&gt;
   emails log, raw events)          ranked, paced, failover          (instant)&lt;br&gt;
Workers receive the webhook and answer in milliseconds.&lt;br&gt;
Workflows hold the journey: "send the confirmation now, the arrival guide 3 days before check-in, a check-in-day note at 08:00 local time, a thank-you after check-out."&lt;br&gt;
D1 is our own record of every contact, booking and email, so nothing depends on a third-party dashboard.&lt;br&gt;
A Durable Object outbox is the single sender, so pacing and quotas live in one place.&lt;br&gt;
Pattern 1 — the event ID is the workflow ID&lt;br&gt;
Webhooks arrive at least once. Two identical deliveries a second apart are normal. The cheapest dedupe we found was to let the workflow engine do it:&lt;/p&gt;

&lt;p&gt;export default {&lt;br&gt;
  async fetch(request, env) {&lt;br&gt;
    const event = await request.json();&lt;br&gt;
    // Same event delivered twice → same instance id → the second create is rejected.&lt;br&gt;
    await env.JOURNEY.create({&lt;br&gt;
      id: &lt;code&gt;e-${event.event_id}&lt;/code&gt;,&lt;br&gt;
      params: { ...event, receivedAt: Date.now() },&lt;br&gt;
    }).catch(() =&amp;gt; {});&lt;br&gt;
    return new Response("ok"); // answer fast; the work happens in the workflow&lt;br&gt;
  },&lt;br&gt;
};&lt;br&gt;
Note receivedAt: it matters later (see bug #4).&lt;/p&gt;

&lt;p&gt;Pattern 2 — "send once" per booking and kind&lt;br&gt;
Inside a journey, retries are a feature. A step that sends an email and then times out will run again. So every customer email goes through one helper:&lt;/p&gt;

&lt;p&gt;async function sendOnce(env, step, kind, ref, build) {&lt;br&gt;
  return step.do(&lt;code&gt;email ${kind}&lt;/code&gt;, async () =&amp;gt; {&lt;br&gt;
    const key = &lt;code&gt;sent:${kind}:${ref}&lt;/code&gt;;           // e.g. sent:hotel_confirmation:BK123&lt;br&gt;
    if (await env.KV.get(key)) return "duplicate";&lt;br&gt;
    await deliver(env, await build());&lt;br&gt;
    await env.KV.put(key, "1", { expirationTtl: 3 * 86400 });&lt;br&gt;
    return "sent";&lt;br&gt;
  });&lt;br&gt;
}&lt;br&gt;
If the email still fails after retries, the journey continues and the owner gets an alert. One broken email must never stop the arrival guide three weeks later.&lt;/p&gt;

&lt;p&gt;Pattern 3 — sleeping longer than a workflow allows&lt;br&gt;
A booking made in January for a trip in December needs an email 3 days before check-in. Workflow sleeps have a maximum of 365 days, and some bookings are further out than that. We chunk the wait, computing every target from fixed values so a replay gives the same answer:&lt;/p&gt;

&lt;p&gt;async function sleepUntilSafe(step, name, when) {&lt;br&gt;
  const MAX = 360 * 86_400_000;&lt;br&gt;
  const start = await step.do(&lt;code&gt;${name}: start&lt;/code&gt;, async () =&amp;gt; Date.now());&lt;br&gt;
  for (let t = start + MAX, i = 0; t &amp;lt; when; t += MAX, i++) {&lt;br&gt;
    await step.sleepUntil(&lt;code&gt;${name} #${i}&lt;/code&gt;, new Date(t));&lt;br&gt;
  }&lt;br&gt;
  await step.sleepUntil(name, new Date(when));&lt;br&gt;
}&lt;br&gt;
We also send check-in and check-out emails at the guest's local morning. The timezone comes from the hotel's longitude, which is close enough for "08:00 at the hotel" without a timezone API call per booking.&lt;/p&gt;

&lt;p&gt;Pattern 4 — one outbox, ranked and paced&lt;br&gt;
Our main email provider has an unpublished daily quota. The first time we ran a marketing send, it used the whole quota by lunchtime, and a booking confirmation that afternoon had to wait. That is the worst possible order.&lt;/p&gt;

&lt;p&gt;The fix was one Durable Object that every client email passes through:&lt;/p&gt;

&lt;p&gt;// Rank: 0 urgent (flight change, check-in) · 1 booking · 2 service · 3 marketing&lt;br&gt;
async alarm() {&lt;br&gt;
  const items = await this.state.storage.list({ prefix: "q:" });&lt;br&gt;
  const next = pickHighestRankThenOldest(items);   // marketing never jumps a booking&lt;br&gt;
  const result = await this.sendWithFailover(next); // main provider → SMTP when the daily quota is hit&lt;br&gt;
  if (result.sent) await this.state.storage.delete(next.key);&lt;br&gt;
  const gap = 150_000 + Math.random() * 10_000;     // one email every 150–160 s&lt;br&gt;
  await this.state.storage.setAlarm(Date.now() + gap);&lt;br&gt;
}&lt;br&gt;
Details that paid off:&lt;/p&gt;

&lt;p&gt;Owner alerts skip the line. The person running the business sees a new booking instantly. Customers get their email within one slot.&lt;br&gt;
Marketing has its own rules: a daily cap, and at most one marketing email per person per day.&lt;br&gt;
Failover is per error, not per day. "Daily limit reached" pauses the main provider and switches to SMTP over cloudflare:sockets. "Recipient invalid" drops the email and alerts the owner. Everything else retries with backoff.&lt;br&gt;
The queue is durable. Deploys, restarts and quota walls never lose an email; it just waits.&lt;br&gt;
Pattern 5 — the declined card your webhook never hears about&lt;br&gt;
This one surprised us. When a customer's card is declined at checkout ("insufficient funds", or the payment processor's fraud check blocking it), the payment fails inside the hosted payment form. No booking request is ever made, so no "booking failed" webhook is ever sent. From the backend, a declined card looks exactly like a customer who closed the tab.&lt;/p&gt;

&lt;p&gt;What we do receive is the prebook (checkout started) with a payment transaction ID. So the prebook journey waits, then checks:&lt;/p&gt;

&lt;p&gt;// inside the "checkout started" workflow&lt;br&gt;
await noteAttempt(env, step, attemptKey, prebookId, receivedAt);&lt;br&gt;
await step.sleep("wait for payment", "20 minutes");&lt;br&gt;
const state = await step.do("completed?", () =&amp;gt;&lt;br&gt;
  checkoutState(env, attemptKey, prebookId, receivedAt)); // booked · newer attempt · unpaid&lt;br&gt;
if (state === "unpaid") await notCompletedFollowUp(env, step, { prebookId, risky });&lt;br&gt;
Three rules keep it honest:&lt;/p&gt;

&lt;p&gt;Only the newest attempt follows up. Someone who tries three cards in ten minutes gets one email, 20 minutes after the last try.&lt;br&gt;
The customer email never claims a decline we can't see. It says "your booking wasn't completed and nothing was charged", then explains the two common decline reasons and how to fix each.&lt;br&gt;
Card testing is held for a human. One email address under different names, or one name with three or more addresses on the same day, means no automatic email. The owner gets an alert with one-click buttons to send the exact reason ("insufficient funds", "blocked by security check") after checking the payment dashboard.&lt;br&gt;
Five bugs that taught us the most&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A rate limiter that didn't limit. We counted failed admin logins in KV. KV is eventually consistent, so five fast attempts from one IP could each read "0". Counters that must be exact now live in D1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"Your flight changed!" when nothing changed. We detected changes by hashing a snapshot of the booking. The snapshot included status, and status moves on its own (CONFIRMED → TICKETED), so guests got false "schedule changed" emails. Snapshots now contain only what a traveller cares about: times, flight numbers, room, hotel confirmation number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A loop that never ended — in tests. The first sleepUntilSafe called itself in a refactor, and our mocked step.sleep returned instantly, so the test hung forever. Every loop around a sleep now has a hard iteration cap.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Date.now() lies after a sleep. A workflow function is replayed when it resumes, and values computed outside a step are recomputed. "Has the customer booked since now?" quietly became "since 20 minutes later". Use a timestamp from the event (receivedAt) or one returned by step.do, never a bare Date.now() that you compare after a sleep.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Internal links in customer emails. One template accidentally carried an admin link meant for the owner. Now deliver() refuses any client email that contains admin URLs or internal system names, and alerts the owner instead. A content lock like that is cheap insurance.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What we would do again&lt;br&gt;
Durable everything: workflows for journeys, a Durable Object for the outbox, D1 for records.&lt;br&gt;
Idempotency at every layer: event ID as workflow ID, send-once keys, "newest attempt only".&lt;br&gt;
A human in the loop for money and fraud, with one click instead of a dashboard hunt.&lt;br&gt;
Our own database. When a supplier dashboard is down or slow, we still know every guest, booking and email.&lt;br&gt;
MAQAMI LLC is a US-registered travel company. Travellers and travel professionals book hotels and flights on MAQAMI &lt;a href="https://book.maqami.co/" rel="noopener noreferrer"&gt;https://book.maqami.co/&lt;/a&gt; with instant confirmation, and AI assistants can search and plan trips through our MCP server &lt;a href="https://mcp.maqami.co/" rel="noopener noreferrer"&gt;https://mcp.maqami.co/&lt;/a&gt;&lt;br&gt;
 . Questions about the architecture? Ask in the comments — we read every one.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>serverless</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Add hotel and flight search to your AI agent in 2 minutes with a remote MCP server</title>
      <dc:creator>Ahmed Gamal Abdou Ahmed</dc:creator>
      <pubDate>Sat, 03 Oct 2026 15:27:53 +0000</pubDate>
      <link>https://dev.to/ahmed_gamalabdouahmed_f/add-hotel-and-flight-search-to-your-ai-agent-in-2-minutes-with-a-remote-mcp-server-5647</link>
      <guid>https://dev.to/ahmed_gamalabdouahmed_f/add-hotel-and-flight-search-to-your-ai-agent-in-2-minutes-with-a-remote-mcp-server-5647</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: this post is by MAQAMI, the travel booking platform that runs the server used below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask a language model "what's available in Lisbon for Nov 12 to 15, and what does it cost?" and it will usually guess from training data. Getting a real answer normally means a supplier contract, API onboarding, or a scraper that breaks every few weeks.&lt;/p&gt;

&lt;p&gt;The Model Context Protocol (MCP) gives you a shorter path. If someone already exposes a booking engine as MCP tools, your client (or your own agent) can call those tools directly.&lt;/p&gt;

&lt;p&gt;This tutorial connects the &lt;strong&gt;MAQAMI Travel MCP server&lt;/strong&gt;, a public remote endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endpoint:  https://mcp.maqami.co/
Transport: Streamable HTTP
Auth:      none (no API key, no sign-up)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing to install. You add a URL to your client and start asking questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your agent gets
&lt;/h2&gt;

&lt;p&gt;Once connected, the model can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search live hotel rates&lt;/strong&gt; by city, coordinates, place or a hotel list, for your dates, guests and currency (3M+ hotels behind it)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt; by star rating, refundable rates only, board type (for example breakfast included) and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare room options&lt;/strong&gt; at one hotel, with meal plan and cancellation policy per rate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look up places and landmarks&lt;/strong&gt; ("near Shibuya station", "Sultanahmet")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull hotel details&lt;/strong&gt;: description, amenities, photos, location, and guest reviews&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search hotels in plain language&lt;/strong&gt; ("quiet boutique hotel with a spa near the old town"), a beta feature&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search flights&lt;/strong&gt;: one-way, round trip or multi-city, with cabin class and stop filters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the weather&lt;/strong&gt; for a destination and dates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prebook, then book&lt;/strong&gt;: a two-step flow that re-checks the price before anything is confirmed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One practical tip before you start: hotel rate search needs &lt;strong&gt;dates, number of guests, a currency and the guest's nationality&lt;/strong&gt;. Prompts that include all four work first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Connect your client
&lt;/h2&gt;

&lt;p&gt;Pick the one you use. The endpoint for every client is &lt;a href="https://mcp.maqami.co/" rel="noopener noreferrer"&gt;mcp.maqami.co&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http maqami-travel https://mcp.maqami.co/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;/mcp&lt;/code&gt; inside Claude Code to check that it shows as connected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cursor
&lt;/h3&gt;

&lt;p&gt;Add this to &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in your project (or &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; for every project):&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;"maqami-travel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.maqami.co/"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;strong&gt;Customize&lt;/strong&gt; in the sidebar and make sure &lt;code&gt;maqami-travel&lt;/code&gt; is enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code with GitHub Copilot
&lt;/h3&gt;

&lt;p&gt;Add this to &lt;code&gt;.vscode/mcp.json&lt;/code&gt; (note the top-level key is &lt;code&gt;servers&lt;/code&gt;, not &lt;code&gt;mcpServers&lt;/code&gt;). Newer VS Code builds also read a portable &lt;code&gt;.mcp.json&lt;/code&gt; at the project root with an &lt;code&gt;mcpServers&lt;/code&gt; key; see the guides.&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;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maqami-travel"&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.maqami.co/"&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;Open Copilot Chat in agent mode and the tools appear under &lt;strong&gt;Configure Tools&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude (claude.ai, Claude Desktop)
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;Customize → Connectors&lt;/strong&gt;, click &lt;strong&gt;+&lt;/strong&gt;, then &lt;strong&gt;Add custom connector&lt;/strong&gt;. Paste &lt;code&gt;https://mcp.maqami.co/&lt;/code&gt; and click &lt;strong&gt;Add&lt;/strong&gt;. Leave the OAuth fields empty. Enable it per chat from the &lt;strong&gt;+&lt;/strong&gt; button → &lt;strong&gt;Connectors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Remote servers go through the Connectors screen, not &lt;code&gt;claude_desktop_config.json&lt;/code&gt; (that file is for local servers). Free plans can add one custom connector. On Team and Enterprise plans an Owner adds it for the organization first.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatGPT
&lt;/h3&gt;

&lt;p&gt;Developer mode is available on the web for Plus, Pro, Business, Enterprise and Education accounts. Turn it on under &lt;strong&gt;Settings → Security and login → Developer mode&lt;/strong&gt;, create a developer-mode app for a remote MCP server with the URL above, and choose &lt;strong&gt;No Authentication&lt;/strong&gt;. In a chat, pick &lt;strong&gt;Developer mode&lt;/strong&gt; from the &lt;strong&gt;+&lt;/strong&gt; menu and select the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cline and Windsurf
&lt;/h3&gt;

&lt;p&gt;Cline (remote servers need the transport set explicitly):&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;"maqami-travel"&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;"streamableHttp"&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.maqami.co/"&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;Windsurf's Cascade agent reads &lt;code&gt;mcp_config.json&lt;/code&gt; (open it from the MCPs section of the Cascade panel menu) and takes &lt;code&gt;serverUrl&lt;/code&gt; for remote servers:&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;"maqami-travel"&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;"serverUrl"&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.maqami.co/"&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;h3&gt;
  
  
  Your own agent (OpenAI Responses API)
&lt;/h3&gt;

&lt;p&gt;If you're building your own agent, the Responses API can call a remote MCP server for you. Restrict it to the tools you need with &lt;code&gt;allowed_tools&lt;/code&gt;:&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;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# any model that supports the MCP tool
&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="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;mcp&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;server_label&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;maqami&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;server_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;Live hotel rates, hotel details and places.&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;server_url&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;https://mcp.maqami.co/&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;allowed_tools&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;post_hotels_rates&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;get_data_places&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;get_data_hotel&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;require_approval&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;always&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="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find 4-star hotels in Lisbon for Nov 12 to 15, 2 adults, EUR, guest nationality GB.&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;require_approval: "always"&lt;/code&gt; the API returns an &lt;code&gt;mcp_approval_request&lt;/code&gt; first. Approve it by sending an &lt;code&gt;mcp_approval_response&lt;/code&gt; with &lt;code&gt;previous_response_id&lt;/code&gt;, or set &lt;code&gt;require_approval&lt;/code&gt; to &lt;code&gt;"never"&lt;/code&gt; for read-only tools once you trust the server. The OpenAI Agents SDK wraps the same config in &lt;code&gt;HostedMCPTool(tool_config={...})&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Try these prompts
&lt;/h2&gt;

&lt;p&gt;Copy any of these into your client once it's connected:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hotel search with the four inputs it needs&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find 4-star hotels in Lisbon for Nov 12 to 15, 2 adults, prices in EUR. I'm a UK resident. Show the 5 lowest total prices."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compare rooms and cancellation terms&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"For the second hotel, show up to 3 room options with the meal plan, total price and free-cancellation deadline for each."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plain-language search plus reviews&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find a quiet boutique hotel with a spa near the old town in Dubrovnik. Show details and photos for the best match and summarize what guests say about noise."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flights and a hotel in one conversation&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Economy flights from Cairo to Istanbul, out Nov 12 and back Nov 16, 1 adult, USD, nonstop only. Then find 4-star hotels in Sultanahmet for those nights, 1 adult, USD, nationality EG."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Place lookup plus weather&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Look up Shibuya Station in Tokyo, show hotels within 1 km with their lowest rate for Dec 3 to 5, 1 adult, USD, nationality US, and tell me the weather forecast for those dates."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;You get real hotels with IDs, live rates in your currency, room names, meal plans and cancellation policies, plus hotel details, reviews and weather. Flight results include carriers, times, stops and fares. Your client shows the arguments and raw response of every call, which is handy for debugging.&lt;/p&gt;

&lt;p&gt;Rates are live, so re-run a search before you quote a price to anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things to know
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Booking tools create real reservations.&lt;/strong&gt; The server has a prebook → book flow. Prebook re-checks the price and shows the cancellation policy; book confirms the reservation with guest and payment details. If you're only prototyping search, keep tool approval on (most clients ask before running a tool by default, and ChatGPT developer mode asks for any tool not marked read-only) or switch the booking tools off in your client's per-tool settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shorter tool lists work better.&lt;/strong&gt; The server exposes a broad tool set. If you only need search, turn off what you don't use (or use &lt;code&gt;allowed_tools&lt;/code&gt; in the Responses API). Models pick the right tool more reliably from a short list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;One URL, no key, and your agent can answer "what's available and what does it cost?" from live data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint: &lt;strong&gt;&lt;a href="https://mcp.maqami.co/" rel="noopener noreferrer"&gt;https://mcp.maqami.co/&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Listings: the official MCP Registry (&lt;code&gt;io.github.negm17111995/maqami-travel&lt;/code&gt;), Glama and mcp.so&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback on tool naming and descriptions is very welcome, especially from people building travel agents. Connect here: &lt;strong&gt;&lt;a href="https://mcp.maqami.co/" rel="noopener noreferrer"&gt;https://mcp.maqami.co/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>travel</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to give Claude the ability to book hotels in 3 minutes</title>
      <dc:creator>Ahmed Gamal Abdou Ahmed</dc:creator>
      <pubDate>Fri, 03 Jul 2026 21:54:25 +0000</pubDate>
      <link>https://dev.to/ahmed_gamalabdouahmed_f/how-to-give-claude-the-ability-to-book-hotels-in-3-minutes-5dn7</link>
      <guid>https://dev.to/ahmed_gamalabdouahmed_f/how-to-give-claude-the-ability-to-book-hotels-in-3-minutes-5dn7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53x8p9u1fgbl3sufj2oy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53x8p9u1fgbl3sufj2oy.png" alt=" " width="582" height="1342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A quick tutorial on how to use the open-source MAQAMI Travel MCP server to give Claude real-world agency.&lt;br&gt;
AI agents are incredibly smart, but they have one massive limitation: they are trapped in a chat box. They can give you travel advice, but they can't actually book the hotel for you.&lt;/p&gt;

&lt;p&gt;Until now.&lt;/p&gt;

&lt;p&gt;With the release of Anthropic's Model Context Protocol (MCP), we can finally give Claude tools to take action in the real world.&lt;/p&gt;

&lt;p&gt;Today, I'm excited to share an open-source project we built: the MAQAMI Travel MCP Server. It exposes live pricing, availability, and direct booking links for over 3 million hotels across 249 countries directly to your AI agent.&lt;/p&gt;

&lt;p&gt;Here is how you can set it up in under 3 minutes.&lt;/p&gt;

&lt;p&gt;🛠️ What it can do&lt;br&gt;
Once connected, your Claude Desktop app gets access to 65 powerful travel tools. You can literally just type:&lt;/p&gt;

&lt;p&gt;"Find me a 5-star hotel in Dubai near the Burj Khalifa for next weekend under $300 a night."&lt;/p&gt;

&lt;p&gt;Claude will intelligently use the MCP server to:&lt;/p&gt;

&lt;p&gt;Search live inventory.&lt;br&gt;
Fetch the exact pricing and photos.&lt;br&gt;
Generate a direct booking link for you to complete the transaction.&lt;br&gt;
(Insert your screenshot here showing Claude successfully running a hotel search!)&lt;/p&gt;

&lt;p&gt;🚀 How to Install (No API Keys Required)&lt;br&gt;
The best part? You don't need any complex API keys or environment variables to get started. The server is completely public and runs via Server-Sent Events (SSE).&lt;/p&gt;

&lt;p&gt;Step 1: Open your Claude config&lt;br&gt;
If you are using Claude Desktop, open your configuration file:&lt;/p&gt;

&lt;p&gt;Mac: ~/Library/Application Support/Claude/claude_desktop_config.json&lt;br&gt;
Windows: %APPDATA%\Claude\claude_desktop_config.json&lt;br&gt;
Step 2: Add the MAQAMI Server&lt;br&gt;
&lt;a href="https://github.com/negm17111995/mcp-server" rel="noopener noreferrer"&gt;https://github.com/negm17111995/mcp-server&lt;/a&gt;&lt;br&gt;
Copy and paste this snippet into your mcpServers object:&lt;/p&gt;

&lt;p&gt;json&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "maqami-travel": {&lt;br&gt;
      "command": "npx",&lt;br&gt;
      "args": [&lt;br&gt;
        "-y",&lt;br&gt;
        "@modelcontextprotocol/server-sse",&lt;br&gt;
        "&lt;a href="https://mcp.maqami.co/sse" rel="noopener noreferrer"&gt;https://mcp.maqami.co/sse&lt;/a&gt;"&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Step 3: Restart Claude&lt;br&gt;
Completely close the Claude Desktop app and open it again. You will now see a little "hammer" icon indicating that Claude has access to the MAQAMI tools!&lt;/p&gt;

&lt;p&gt;🧑‍💻 The Magic Behind It&lt;br&gt;
If you're curious about how this works under the hood, the server exposes endpoints like post_hotels_rates and get_data_hotel. When you ask a natural language question, Claude figures out the exact parameters (like destination codes, check-in dates, and currency) and fires the API request through the MCP proxy.&lt;/p&gt;

&lt;p&gt;It effectively turns Claude into a fully autonomous travel agent that doesn't hallucinate pricing, because it's fetching real-time data from the MAQAMI global inventory.&lt;/p&gt;

&lt;p&gt;🔗 Try it out and contribute!&lt;br&gt;
We are just scratching the surface of what Agentic AI can do.&lt;/p&gt;

&lt;p&gt;If you want to play around with it, you can check out the source code and documentation on GitHub: 👉 MAQAMI MCP Server Repository&lt;/p&gt;

&lt;p&gt;Let me know in the comments if you build anything cool with it, or if you have any feature requests! If you find it useful, a ⭐️ on GitHub goes a long way.&lt;/p&gt;

&lt;p&gt;Happy building! 🚀&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>claude</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
