Skip to main content
Glama

Restaurant Reservations — MCP Tool

A small Laravel application that exposes restaurant booking as an MCP tool, so a customer (or an AI agent acting for them) can reserve a table. Every call answers three things:

  • whether the requested date and time can be seated,

  • the confirmed reservation (reference, date, time, party size),

  • the nearest alternative times when the requested slot cannot take the party.

It is built to absorb many concurrent calls: Octane/Swoole keeps the framework booted in memory, and the capacity check is a single atomic Redis operation, so the same seat can never be sold twice.

Measured: 1,584 req/s with zero errors (p95 140 ms), and exactly 40 of 40 seats sold when 12,001 requests fought over one slot. Full method and numbers in How I tested performance and what the results were.

Want to check every claim in this README yourself? VERIFY.md is a copy-paste checklist: each requirement from the brief, the command that proves it, and what a pass looks like.


Contents

The sections the brief asks for come first, then how to connect an AI agent over MCP, then the constraints and bonus items, then deeper reference material.

The brief, answered directly

  1. How to start the project with Sail

  2. How to call the MCP tool manually

  3. Setting up the MCP with an agent (Claude, Codex, and others)

  4. How I tested performance and what the results were

  5. Assumptions or tradeoffs

How this submission maps to the brief

  1. Constraints

  2. Bonus features

Reference

  1. How it works

  2. Tests

  3. How this would scale

  4. Configuration


Related MCP server: @striderlabs/mcp-opentable

How to start the project with Sail

Requirements: Docker only (Docker Desktop on macOS/Windows). No local PHP, Composer or Node is needed — everything runs inside the Sail containers.

# 1. Install PHP dependencies using a throwaway container (only needed once)
docker run --rm -v "$(pwd)":/var/www/html -w /var/www/html \
    laravelsail/php84-composer:latest composer install --ignore-platform-reqs

# 2. Create your environment file
cp .env.example .env

# 3. Start everything
./vendor/bin/sail up

On the one manual step. vendor/ is not committed, so step 1 installs dependencies once via a throwaway container — no local PHP or Composer required. Beyond that single command and copying .env, there is no manual setup: migrations and key generation happen automatically on boot, so adding environment variables is all that stands between a clone and a running server.

That is the whole setup. On boot the container:

  • generates APP_KEY if the env file has none,

  • waits for MySQL to accept connections,

  • runs migrations,

  • and starts Octane (Swoole).

So there is no separate ./vendor/bin/sail artisan migrate step and no second terminal — a single sail up gives you a working server. The boot sequence lives in docker/start-app.sh if you want to read exactly what happens.

The MCP endpoint is then available at http://localhost/mcp/reservations. A quick smoke test that lists the exposed tools:

curl -s -X POST http://localhost/mcp/reservations \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Common follow-up commands:

./vendor/bin/sail artisan test          # run the test suite
./vendor/bin/sail artisan reservations:verify   # audit Redis counters against MySQL
./vendor/bin/sail down                  # stop everything
./vendor/bin/sail down -v               # stop and wipe the MySQL/Redis volumes

Note on .env changes. Octane holds the environment in memory between requests, so editing .env while the server is running has no effect until you restart it with ./vendor/bin/sail restart.


How to call the MCP tool manually

Two tools are exposed on the reservation server. Both are plain JSON-RPC 2.0 over a single HTTP POST — no session handshake is required, which keeps curl examples simple and also makes the endpoint straightforward to load test.

Tool

Purpose

create-reservation

Book a table (or be told why it can't be booked, with alternatives).

check-availability

List every bookable start time on a date and the seats left in each.

create-reservation

curl -s -X POST http://localhost/mcp/reservations \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "create-reservation",
      "arguments": {
        "customer_name": "Ada Lovelace",
        "customer_email": "ada@example.com",
        "customer_phone": "+44 7700 900123",
        "party_size": 2,
        "date": "2026-09-20",
        "time": "19:00",
        "special_requests": "Window table if possible",
        "idempotency_key": "booking-attempt-001"
      }
    }
  }'

Arguments

Field

Required

Rule

customer_name

yes

string, 2–120 chars

customer_email

yes

valid email, ≤180 chars

customer_phone

no

string, ≤40 chars

party_size

yes

integer, 1–12

date

yes

YYYY-MM-DD

time

yes

HH:MM, 24-hour, on the 30-minute grid

special_requests

no

string, ≤500 chars

idempotency_key

no

string, ≤64 chars; a repeat with the same key returns the original booking

Input is validated before anything is booked; failures come back as an MCP tool error listing exactly what was wrong.

Confirmed response

{
  "status": "confirmed",
  "message": "Table confirmed for 2 on Sunday 20 September at 7:00 PM. 38 seat(s) left in that slot.",
  "reservation": {
    "reference": "RSV-LZEZUGIXZ8",
    "date": "2026-09-20",
    "time": "19:00",
    "ends_at": "20:30",
    "party_size": 2,
    "customer_name": "Ada Lovelace",
    "customer_email": "ada@example.com",
    "timezone": "UTC"
  },
  "alternatives": [],
  "already_booked": false
}

Slot full — the agent is handed times it can offer instead:

{
  "status": "unavailable",
  "message": "7:00 PM on Sunday 20 September is fully booked for a party of 2.",
  "reservation": null,
  "alternatives": [
    { "date": "2026-09-20", "time": "19:30" },
    { "date": "2026-09-20", "time": "18:30" },
    { "date": "2026-09-20", "time": "20:00" }
  ],
  "already_booked": false
}

Status values

Status

Meaning

confirmed

Table booked; reservation is populated.

unavailable

The slot exists but cannot seat this party right now. Offer alternatives.

rejected

Never bookable as asked — closed, in the past, off the 30-minute grid, or a party too large. message explains which.

check-availability

curl -s -X POST http://localhost/mcp/reservations \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"check-availability","arguments":{"date":"2026-09-20","party_size":4}}}'

Returns each bookable start time on that date with the number of seats still free in it (filtered to those that can seat the requested party_size).

From a voice agent (Vapi / Retell)

A phone agent can book through POST /webhooks/voice/reservation, which maps the platform's function-call payload onto the same ReservationService — so capacity, alternatives and idempotency behave identically whether the guest typed or spoke. Set VOICE_WEBHOOK_SECRET first; an empty secret disables the endpoint rather than leaving it open.

curl -s -X POST http://localhost/webhooks/voice/reservation \
  -H 'Content-Type: application/json' \
  -H 'X-Vapi-Secret: local-voice-secret' \
  -d '{
    "message": {
      "type": "tool-calls",
      "call": { "id": "call_123" },
      "toolCalls": [{
        "id": "toolcall_abc",
        "function": {
          "name": "create_reservation",
          "arguments": {
            "customer_name": "Phone Caller",
            "customer_phone": "+44 7700 900999",
            "party_size": 3,
            "date": "2026-09-20",
            "time": "21:00"
          }
        }
      }]
    }
  }'
{
  "results": [{
    "toolCallId": "toolcall_abc",
    "result": "Table confirmed for 3 on Sunday 20 September at 9:00 PM. 37 seat(s) left in that slot. The reference is R S V B R U 8 R K E C 5 0."
  }],
  "response": "Table confirmed for 3 ...",
  "status": "confirmed",
  "reference": "RSV-BRU8RKEC50",
  "alternatives": []
}

Details that matter on a phone call:

  • Both platforms are accepted. Vapi's message.toolCalls[] envelope (with arguments as an object or a JSON string) and Retell's flat {name, args, call} body both work, and the reply carries Vapi's results envelope alongside the top-level fields Retell reads.

  • Signed, or refused. Vapi's shared-secret header (X-Vapi-Secret) and Retell's body HMAC (X-Retell-Signature) are both verified, with timing-safe comparisons.

  • Email is optional here — a caller has a phone number. At least one contact method is required. The MCP tool still requires an email.

  • The tool-call id becomes the idempotency key, so a replayed turn cannot book twice.

  • References are spelled out (R S V B R U 8…) so text-to-speech reads characters rather than inventing a word.

  • Validation failures come back as a spoken sentence with HTTP 422, so the agent can simply ask the caller again.

To test against the real platform locally, expose the app with a tunnel (cloudflared tunnel --url http://localhost) and point the platform's function URL at it.


Setting up the MCP with an agent (Claude, Codex, and others)

This is how an AI agent — Claude, Codex, or any MCP-compatible client — connects to the two reservation tools and books on a guest's behalf.

Both tools are served two ways from routes/ai.php: over HTTP at http://localhost/mcp/reservations (simplest for any client that speaks streamable HTTP) and over stdio via php artisan mcp:start reservations (for clients that launch a local process). The server runs inside the container, so start the stack before connecting any client:

./vendor/bin/sail up -d

In the examples below, replace /absolute/path/to/reservations with the absolute path to your clone (e.g. the output of pwd).

The least fiddly option, because it just points at the running HTTP endpoint:

claude mcp add --transport http reservations http://localhost/mcp/reservations
claude mcp list          # expect: reservations ... ✓ Connected

Then start claude, type /mcp to confirm the two tools loaded, and ask in plain English, e.g. "book a table for 2 tomorrow at 7pm for Ada, ada@example.com". Remove it later with claude mcp remove reservations.

Claude Desktop (stdio)

Edit Claude Desktop's config file — ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows — and add a reservations server inside mcpServers (keep any existing keys):

{
  "mcpServers": {
    "reservations": {
      "command": "docker",
      "args": [
        "compose",
        "-f", "/absolute/path/to/reservations/compose.yaml",
        "exec", "-T",
        "laravel.test",
        "php", "artisan", "mcp:start", "reservations"
      ],
      "env": { "WWWGROUP": "1000", "WWWUSER": "501" }
    }
  }
}

Then fully quit Claude Desktop (Cmd+Q / Quit, not just close the window) and reopen it. The reservations connector should report connected, with create-reservation and check-availability available.

Why call docker compose directly instead of ./vendor/bin/sail? A desktop client launches the command from its own working directory, not the project, so a relative ./vendor/bin/sail — and Sail's implicit "find compose.yaml in the current directory" — fails with no configuration file provided: not found followed by Sail is not running. Pinning the compose file with -f <absolute path> makes the working directory irrelevant. -T disables the pseudo-TTY that would otherwise corrupt the JSON-RPC stream, and the two env values silence Sail's WWWGROUP/WWWUSER warnings. The containers must already be running (sail up -d) whenever the client launches; after a sail down or reboot, bring them back up and relaunch the client.

Codex CLI (stdio)

Codex reads ~/.codex/config.toml. Add the server there:

[mcp_servers.reservations]
command = "docker"
args = [
  "compose",
  "-f", "/absolute/path/to/reservations/compose.yaml",
  "exec", "-T",
  "laravel.test",
  "php", "artisan", "mcp:start", "reservations",
]

[mcp_servers.reservations.env]
WWWGROUP = "1000"
WWWUSER = "501"

Or register it from the command line:

codex mcp add reservations -- \
  docker compose -f /absolute/path/to/reservations/compose.yaml \
  exec -T laravel.test php artisan mcp:start reservations

The same rules as Claude Desktop apply: containers up first, -f <absolute> so the working directory does not matter, and -T to keep stdout clean. Recent Codex builds can also connect over HTTP instead of stdio — point those at http://localhost/mcp/reservations.

MCP Inspector

To poke at the tools interactively without any client, use the bundled inspector:

./vendor/bin/sail artisan mcp:inspector mcp/reservations

How I tested performance and what the results were

Method

Load was generated with k6 running in Docker, attached to the app's own Compose network so the numbers measure the application rather than Docker Desktop's port forwarding:

# Throughput: bookings spread across the calendar
docker run --rm -i --network test_sail -v "$PWD/load-test:/scripts" \
    -e BASE_URL=http://laravel.test grafana/k6 run /scripts/spread.js

# Contention: every request fights for the SAME slot
docker run --rm -i --network test_sail -v "$PWD/load-test:/scripts" \
    -e BASE_URL=http://laravel.test grafana/k6 run /scripts/hot-slot.js

# Then prove nothing was oversold
./vendor/bin/sail artisan reservations:verify

(-e BASE_URL=http://host.docker.internal without --network also works, via the published port.)

Between runs the state was reset with sail artisan migrate:fresh --force and sail exec redis redis-cli flushall.

Hardware: MacBook, 11 CPUs available to Docker, 8 GB VM. Octane ran 11 Swoole workers. spread.js ramps 10 → 200 VUs over 70 s; hot-slot.js holds a constant 400 requests/s for 30 s against a single slot.

Results

Scenario

Requests

Throughput

Avg

p95

Max

Errors / timeouts

Spread, 40 seats/slot

110,903

1,584 req/s

84 ms

140 ms

796 ms

0

Spread, capacity raised (every call writes a row)

87,180

1,245 req/s

107 ms

171 ms

936 ms

0

Hot slot, 400 req/s at one slot

12,001

400 req/s absorbed

4.4 ms

3–15 ms

312 ms

0

Baseline: Sail's default artisan serve

8,330

119 req/s

1.13 s

1.74 s

2.07 s

0

Reading the table:

  • Throughput. ~111,000 tool calls in 70 seconds with no failed request and no timeout (two consecutive runs measured 1,584 and 1,559 req/s). The second row is the harsher version of the same test: capacity was raised so that every call booked a table, i.e. 87,180 rows written to MySQL, still with zero errors.

  • Correctness under contention. 12,001 requests aimed at one 40-seat slot in 30 seconds sold exactly 40 seats (20 parties of two) — not 39, not 41. The other 11,981 were turned away cleanly, each with alternative times. reservations:verify confirmed it against MySQL:

    | Slot (UTC)       | Seats sold | Bookings | Capacity |
    | 2026-09-12 19:00 | 40         | 20       | 40       |
    No slot exceeds capacity.
    Redis counters match the database.

    Latency drops under contention because a full slot is answered by Redis alone — the rejection never reaches MySQL. Across six repeat runs the median stayed at ~3.3 ms while the p95 wandered between 3 ms and 15 ms; that tail is noise from a laptop running Docker, not the application, so the median is the number to trust here. The seat count came out at exactly 40 every single time, which is the part that actually matters.

  • Runtime choice. The same script against Sail's stock PHP development server manages 119 req/s at a p95 of 1.74 s. Octane/Swoole is roughly 14× the throughput at ~1/13th the latency, which is why the Compose file overrides the serve command.

An earlier run with the rate limit deliberately lowered is worth reporting because it shows the throttle working: throughput flat-lined at the cap (6,000/min × 2 windows) with the rest returning 429. The limit is a safety valve, not a bottleneck — see the tradeoff in Assumptions or tradeoffs.


Assumptions or tradeoffs

Domain simplifications

  • One restaurant, one dining room. Capacity is seats per start time, not individual tables, so there is no table assignment or table-joining logic.

  • A 90-minute turn is recorded but not deducted from later slots. ends_at is stored on the reservation, but a 19:00 booking does not consume 19:30 capacity. Modelling overlap properly means claiming every slot a turn spans — a straightforward extension of the same Lua script, but it adds multi-key atomicity concerns that felt out of scope here.

  • Service hours are a single window per day (17:00–22:00 by default) with no per-weekday rules, holidays or blackout dates.

  • Parties above 12 are rejected and pointed at an events team — a stand-in for the external system such a booking would really go to.

  • Times are restaurant-local wall clock in RESERVATIONS_TIMEZONE (UTC by default) and stored in UTC. There is no per-customer timezone handling.

Engineering tradeoffs

  • Redis is the gate, MySQL is the record. This buys an O(1) capacity check with no hot-row database lock, at the cost of two systems that must agree. The failure paths are handled (compensating release, cold-counter rebuild, reservations:verify to audit), but a crash in the microseconds between claiming a seat and inserting the row would leak seats until that counter is rebuilt.

  • The rate limit default is deliberately high (300000/min) so that load testing measures the application rather than the throttle. A real deployment would set it far lower and key it per authenticated client rather than per IP.

  • No authentication on the MCP endpoint. It is open, which is right for an exercise and wrong for production; laravel/mcp supports Sanctum and OAuth 2.1 middleware on the same route. The voice webhook is authenticated, because an open booking webhook on a public URL is a different kind of mistake.

  • The AI phrasing layer is deliberately cosmetic and off by default. It cannot choose times or book anything, it is cached, and it degrades to the written message — a model is not something I would put in the decision path of a booking.

  • No queue. Booking is fully synchronous so the agent gets a real confirmation rather than a promise. Anything slow that a real system would do here (confirmation email, POS sync) belongs on a queue; nothing like that is mocked in.

  • Octane means state lives between requests. Services are container-bound rather than singletons so each resolution re-reads config, and the code holds no static mutable state.

  • Node/Vite are untouched — there is no UI, as the exercise does not require one.


Constraints

How this submission respects each constraint:

  • Must be a Laravel project, reproducible with Laravel Sail. It is a standard Laravel 13 application (PHP 8.3+, running on the PHP 8.5 Sail runtime); sail up builds and runs the whole stack (app + MySQL + Redis) with no local toolchain.

  • The core deliverable is the MCP tool — keep everything else as small as possible. The reservation domain is a handful of small, single-purpose classes; there is no UI, no auth scaffolding, and the default Laravel example tests and the inspire console stub have been removed. Everything beyond the tool (voice webhook, AI phrasing) is optional and isolated.

  • External services can be mocked. The laravel/ai phrasing layer is off by default and falls back to a written message, and the voice webhook can be exercised locally with curl without any Vapi/Retell account. Redis and MySQL run inside Sail.


Bonus features

Each optional bonus item, and where it lives:

  • laravel/ai. With RESERVATIONS_AI_PHRASING=true, the "fully booked" sentence is rewritten by a HostAgent so a turned-away guest hears something warmer than a template. The design point is what the model is not allowed to do: which times to offer is decided by SlotCalendar and SlotAvailability, and the model only phrases that decided list, so a bad generation is a clumsy sentence rather than a wrong booking. It is cached per slot and party size, capped at 40 words, falls back to the written message on any error, and is off by default (it would otherwise put a network call on the fastest path in the app and make the benchmarks untrue). Enabling it needs a provider key such as ANTHROPIC_API_KEY.

  • Voice agent integration (Vapi / Retell). POST /webhooks/voice/reservation accepts both platforms' function-call payloads against the same reservation service — see From a voice agent. Signatures are verified and fail closed.

  • Caching. check-availability reads through a 5-second Redis cache (RESERVATIONS_AVAILABILITY_CACHE_SECONDS), so repeated "what's free tonight?" questions cost nothing. Bookings deliberately never read that cache — they always go through the atomic claim — so a stale read can never cause an overbooking.

  • Rate limiting. The MCP route carries throttle:mcp, defined in AppServiceProvider and keyed by IP, with the limit from MCP_RATE_LIMIT_PER_MINUTE; responses carry X-RateLimit-Limit / X-RateLimit-Remaining. The voice webhook gets its own, far tighter throttle:voice — a phone line makes a handful of calls a minute, not thousands.

  • How I would scale this. A dedicated section: How this would scale.


How it works

MCP client ──POST /mcp/reservations──▶ Octane/Swoole worker (app stays booted in memory)
                                         │
                          throttle:mcp ──┤
                                         ▼
                              CreateReservationTool          validation + MCP schema
                                         ▼
                               ReservationService            the only place bookings happen
                             ┌───────────┴────────────┐
                             ▼                        ▼
                   SlotAvailability (Redis)    Reservation (MySQL)
                   Lua: check + INCRBY         durable record, unique
                   in ONE atomic call          idempotency_key

Why it is safe under concurrency. Seats sold per slot live in a Redis counter, and the only thing allowed to move that counter is a Lua script that reads capacity, compares, and increments inside a single Redis call. Redis executes scripts atomically, so two workers chasing the last two seats cannot both win — there is no read-then-write window to lose, and no database row lock to serialise on. The reservation row is written to MySQL afterwards; if that write fails the seats are released again.

Why MySQL still matters. Redis is a fast gate, not the record. If a counter is missing (Redis restarted, key expired), it is rebuilt from SUM(party_size) in MySQL and published with SET ... NX, so the first worker through wins the race and later ones do not clobber claims made in between. A test covers exactly this (test_availability_counters_survive_a_cold_cache).

Alternatives are found by asking Redis for the surrounding slots in one MGET, then keeping the closest ones that still fit the party — one round trip, not one per candidate.

Idempotency. idempotency_key is a unique column. A retrying agent gets the original reservation back (already_booked: true) instead of a second table, and a race on the same key is caught at the constraint and compensated.

Files worth looking at

Path

What it holds

app/Mcp/Tools/CreateReservationTool.php

The MCP tool: input schema, validation, output schema

app/Mcp/Servers/ReservationServer.php

Server definition and the instructions agents read

app/Services/Reservations/SlotAvailability.php

The Redis Lua capacity gate

app/Services/Reservations/ReservationService.php

Booking flow, idempotency, compensation

app/Services/Reservations/PendingReservation.php

The requested booking, typed; owns the field→column mapping

app/Services/Reservations/SlotCalendar.php

Slot grid, service hours, candidate alternatives

app/Services/Reservations/RestaurantPolicy.php

The single source of truth: every setting and every input rule

config/reservations.php

Every restaurant rule, all env-backed

docker/start-app.sh

Boot: key, migrate, then Octane

app/Http/Controllers/VoiceReservationController.php

Voice platform payload → the same service

app/Http/Middleware/VerifyVoiceSignature.php

Webhook authentication, fails closed

app/Services/Reservations/AlternativePhrasing.php

Optional laravel/ai wording layer


Tests

./vendor/bin/sail artisan test

21 feature tests, driving the tools through the MCP layer itself and the webhook over HTTP:

  • Booking — a slot that fills exactly to capacity and not one seat further, alternatives when full, idempotent retries, rejected times (including past dates), input validation, counter rebuilding after Redis is flushed.

  • Voice — both platform payload shapes, signature verification (unsigned, mis-signed and unconfigured all refused), contact-detail rules, spoken validation errors, replayed turns.

  • AI phrasing — that an enabled model rewrites only the sentence and never the offered times, and that a failing or rambling provider falls back to the written message.

Static analysis and formatting are also wired up: composer analyse (PHPStan/Larastan level 6) and composer test:lint (Pint), and both run in CI (.github/workflows/ci.yml).


How this would scale

The current shape already removes the usual bottleneck: capacity is decided by one atomic Redis call, not by a database row lock, so concurrent bookings for the same slot do not queue behind each other in MySQL. Scaling from there is mostly horizontal.

Next 10×. Run several Octane containers behind a load balancer. They are stateless — all shared state is in Redis and MySQL — so this is a replica count, nothing more. MySQL sees one small INSERT per confirmed booking and nothing at all for a rejection, and availability reads are absorbed by the cache, so a single primary carries this comfortably.

Next 100×. Two changes. First, move the durable write off the request path: the Redis claim is what actually reserves the seat, so the row can be written by a queued job and the guest still gets an immediate, truthful confirmation. That converts bursts into queue depth instead of database pressure, at the cost of a short window where a reservation exists in Redis but not yet in MySQL — acceptable for a booking, not for a payment. Second, split reads from writes with a replica serving the counter-rebuild query and any reporting.

Beyond that, slot counters shard naturally: the key is slot:<timestamp>, so a Redis Cluster spreads them across nodes with no coordination between slots, and a multi-venue tenant would prefix by restaurant. The per-key ceiling is Redis's single-threaded throughput — far past what any one dining room can physically seat.

What I would add before any of that: authentication on the MCP endpoint (Sanctum or OAuth 2.1, both supported by laravel/mcp), per-client rather than per-IP rate limits, and metrics on claim latency and rejection rate so saturation shows up before guests feel it.


Configuration

Everything is env-backed; see .env.example and config/reservations.php.

Variable

Default

Meaning

RESERVATIONS_TIMEZONE

UTC

Timezone all dates/times are interpreted in

RESERVATIONS_OPENING_TIME / CLOSING_TIME

17:00 / 22:00

First and last bookable seating

RESERVATIONS_SLOT_MINUTES

30

Spacing of the slot grid

RESERVATIONS_TURN_MINUTES

90

How long a table is held (informational)

RESERVATIONS_SEATS_PER_SLOT

40

Capacity per start time

RESERVATIONS_MAX_PARTY_SIZE

12

Largest party bookable online

RESERVATIONS_ALTERNATIVE_WINDOW_SLOTS

4

How far either side to search (4 × 30 min = ±2 h)

RESERVATIONS_AVAILABILITY_CACHE_SECONDS

5

TTL of the availability read cache

MCP_RATE_LIMIT_PER_MINUTE

300000

Per-IP cap on the MCP endpoint

VOICE_WEBHOOK_SECRET

(empty)

Shared secret for the voice webhook; empty disables it

VOICE_RATE_LIMIT_PER_MINUTE

120

Per-IP cap on the voice webhook

RESERVATIONS_AI_PHRASING

false

Let laravel/ai phrase the "fully booked" line

RESERVATIONS_AI_CACHE_SECONDS

300

How long a generated sentence is reused

OCTANE_WORKERS

auto

Swoole workers (auto = one per CPU)

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables restaurant reservation management through SevenRooms API, allowing users to create reservations and query available time slots with guest details and party size information.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables managing restaurant reservations over Streamable HTTP, with tools for booking, checking availability, cancelling, resources for reservation data, and prompts for drafting confirmations.
    MIT