OpenBook
by Sanket1815
README.md
# OpenBook
Open-source appointment booking for small businesses (barbershops, salons, studios), built for
voice. Customers book, reschedule and cancel by talking to Alexa+ or any other MCP client; the
assistant talks to OpenBook through a **Model Context Protocol server**.
> 🧑 "Book a fade with Marcus at Fade Studio Saturday afternoon."
> 🔊 "Marcus has three times available Saturday afternoon: 12:30 PM, 1:15 PM, or 3 PM. Which one
> works for you?"
>
> _(a real reply from the simulated Alexa+: Claude on Amazon Bedrock calling `list_services` and
> `search_availability` on the OpenBook MCP server)_
Built for the Amazon Developer Hackathon (Alexa+ track, AWS Builder and Open Source challenges).
## Status
OpenBook is being built in milestones. This section is kept honest: only what is listed as done
exists and is tested.
| # | Milestone | Status |
| --- | ------------------------------------------------------------------------------- | ------------ |
| 1 | Foundation: monorepo, Postgres schema with exclusion constraints, seed data, CI | ✅ Done |
| 2 | Core domain: availability, holds, bookings, reschedule, cancel, idempotency | ✅ Done |
| 3 | MCP server (Streamable HTTP, 8 tools, dev auth) | ✅ Done |
| 4 | Simulated Alexa+ (`/assistant`, Bedrock agent + MCP client) | ✅ Done |
| 5 | Owner dashboard | ✅ Done |
| 6 | Notifications + outbox worker | ✅ Done |
| 7 | Cognito OAuth + AWS CDK deployment + connect to real Alexa+ (MCP Toolkit) | ✅ Deployed² |
| 8 | Stretch: Bedrock Knowledge Base FAQ, Google Calendar | ✅ Built¹ |
| 9 | Polish: README, architecture, demo script, friction log, product feedback | ✅ Done |
| 10 | Store finder: live "salons near me" search (OpenBook + real-world shops) | ✅ Done |
¹ Code and tests done; the Knowledge Base is created on AWS deploy, and Google Calendar needs a
Google OAuth client, so neither has been exercised against the live service yet.
² Deployed to AWS (us-east-1) on 2026-09-24: API health, the MCP OAuth 401 challenge and
metadata, migrations + seed, and the web app verified live. The Alexa+ registration still needs
the project owner's Alexa developer account.
Architecture: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). Every design decision:
[docs/DECISIONS.md](docs/DECISIONS.md).
What works today:
- **End to end by voice:** `docker compose up`, open `http://localhost:3000/assistant`, hold the
button and talk. The simulated Alexa+ (Claude Haiku 4.5 on **Amazon Bedrock**, Converse API
with tool use) books, reschedules and cancels through the OpenBook MCP server, answers questions
from the business's policies, and shows every MCP tool call with its arguments and result.
Verified live: book → parking question → reschedule → list → cancel with the $10 late fee
quoted first.
- **Owner dashboard** at `http://localhost:3000/dashboard` (local "Dev login" as Fade Studio's
owner): day/week calendar by staff member with cancel and no-show, services, staff with
services and weekly hours, time off, policies and FAQ, settings with the MCP endpoint URL and a
copy button. Backed by an owner REST API (`/api/v1`, same server as MCP) that authorises every
request against `business_owners`. Changes reach the voice assistant immediately. A Playwright
test covers "owner creates a service → it appears in `list_services`".
- **Store finder (live):** choose "Any shop near me" on `/assistant` and ask "find me a barber
near Williamsburg that can do a shave today". OpenBook shops come back with distance, open now and
the next free slot (bookable right there), plus real-world shops from **Amazon Location Service**
(name, distance, hours, phone; not bookable). Served by the directory MCP endpoint `/mcp`.
- **Notifications:** every booking, reschedule and cancellation queues a message in the outbox;
the worker sends a confirmation SMS and email (Amazon SES / AWS End User Messaging in AWS, logged
locally), retrying with backoff and never sending a channel twice.
- `docker compose up` starts Postgres 16, applies migrations, seeds two demo businesses, and runs
the MCP server (:3001) and web app (:3000).
- The database itself rejects double bookings, overlapping holds, bookings inside another
appointment's buffer, and cross-tenant references (see [Data integrity](#data-integrity)).
- **MCP server** at `http://localhost:3001/mcp/{businessSlug}`: Streamable HTTP, stateless, JSON
responses, built on the official MCP TypeScript SDK v2. It serves MCP 2026-07-28 and the
2025-era protocol (including 2025-11-25). All 8 tools are listed in
[docs/MCP_TOOLS.md](docs/MCP_TOOLS.md). Tools return speakable text plus structured data, and
stable error codes.
- OAuth 2.1 resource-server behaviour: `401` + RFC 9728 challenge, JWT validation against a JWKS
(issuer, expiry, audience/client id), per-business protected-resource metadata. Locally,
`DEV_AUTH` signs in as the demo customer Jen. Real Cognito sign-in is milestone 7.
- Verified with **MCP Inspector 2.8.0** (tools/list, tools/call) against the Docker Compose stack.
- `packages/core` holds every booking rule: availability (working hours, buffers, time off,
notice, horizon, DST-safe), 5-minute holds, booking, rescheduling, cancellation with the late
fee, idempotent writes, a transactional outbox, service/staff matching for spoken requests, and
policy-based FAQ answers.
- 165 tests, including 20 concurrent holds for one slot (exactly one wins), concurrent retries with
one idempotency key (one booking), tenant isolation, a full book → reschedule → cancel flow
driven by the SDK client over Streamable HTTP in both protocol eras, and the Bedrock ↔ MCP agent
bridge against the real server (with a scripted model, so CI needs no AWS account).
Not yet: notifications are queued in the outbox but not sent (milestone 6), the owner dashboard
(milestone 5), and AWS deployment with Cognito sign-in (milestone 7). The assistant signs in as
the demo customer Jen with the development token.
## Quick start
Requirements: Docker. For the voice assistant, AWS credentials with Amazon Bedrock access to a
Claude model (your `~/.aws` profile is mounted read-only, or export `AWS_PROFILE` /
`AWS_ACCESS_KEY_ID` etc.). For development also Node.js 24 and pnpm 10.
```sh
docker compose up
```
Then open **http://localhost:3000/assistant** (Chrome or Edge for speech recognition; any browser
works with the text box), hold the button and say "Book a fade with Marcus Saturday afternoon."
This starts Postgres on `localhost:54329`, runs migrations, seeds demo data (only the first time;
your bookings survive restarts), and starts the MCP server on `localhost:3001` and the web app on
`localhost:3000`. The default model is `us.anthropic.claude-haiku-4-5-20251001-v1:0` in
`us-east-1`; set `BEDROCK_MODEL_ID` / `AWS_REGION` to change it. Without Bedrock access the page
still loads and says why it can't answer.
The MCP server works on its own too:
```sh
curl localhost:3001/healthz
npx @modelcontextprotocol/inspector --cli http://localhost:3001/mcp/fade-studio --transport http \
--header "Authorization: Bearer dev-token-jen-0123456789" --method tools/list
```
To wipe and reseed:
```sh
SEED_RESET=true docker compose up
```
### Demo data
| Business | Slug | Time zone | Staff | Services |
| ----------- | ------------- | ------------------- | ------------ | -------------------------------------------------------------------------------------- |
| Fade Studio | `fade-studio` | America/New_York | Marcus, Dana | Fade (45 min, $35), Beard Trim (20 min, $15), Cut & Beard (60 min + 5 min buffer, $45) |
| Glow Salon | `glow-salon` | America/Los_Angeles | Priya | Blowout (45 min, $40), Haircut (60 min + 10 min buffer, $65) |
Fade Studio is open Tuesday–Saturday, 9 AM–6 PM, has a 24-hour cancellation window with a $10
late fee, welcomes walk-ins, and has free street parking on Elm St. The upcoming Saturday is
partly booked so availability looks realistic. Demo customer **Jen** (OAuth subject `dev|jen`)
exists in both businesses as separate customer records.
## Architecture
```mermaid
flowchart LR
subgraph Browser
A["/assistant<br/>push-to-talk (Web Speech API)<br/>speech synthesis · Tool calls panel"]
end
subgraph web["apps/web (Next.js)"]
R["POST /api/assistant<br/>agent loop"]
end
subgraph AWS
B["Amazon Bedrock<br/>Claude · Converse API + tool use"]
end
subgraph mcp["apps/mcp-server (Hono + MCP SDK v2)"]
M["POST /mcp/{businessSlug}<br/>Streamable HTTP · stateless · JSON<br/>OAuth 2.1 resource server"]
T["8 tools<br/>get_business_info · list_services · search_availability · hold_slot<br/>create_booking · find_my_bookings · reschedule_booking · cancel_booking"]
end
C["packages/core<br/>availability · holds · bookings<br/>idempotency · policies"]
D[("PostgreSQL 16<br/>exclusion constraints<br/>outbox")]
X["Any MCP client<br/>(Alexa+, MCP Inspector, desktop assistants)"]
A -- "what the customer said" --> R
R -- "Converse (messages + tool specs)" --> B
B -- "toolUse" --> R
R -- "MCP tools/list, tools/call<br/>Bearer token" --> M
X -- "MCP over Streamable HTTP" --> M
M --> T --> C --> D
```
Each spoken turn: the page sends the transcript to `/api/assistant`, which asks Bedrock what to
do. The model's tool calls go to the MCP server exactly as any MCP client's would, and the reply
is spoken back. The panel under each reply lists those MCP calls.
## Deploy to AWS
`infra/` is an AWS CDK app that deploys everything as one stack: API Gateway + Lambda (MCP
server and owner API), a Lambda container for the web app, RDS PostgreSQL, Cognito, the outbox
worker on an EventBridge schedule, a Bedrock Knowledge Base, and migrations that run on every
deploy. Architecture and trade-offs: [docs/DECISIONS.md](docs/DECISIONS.md) (D39).
```sh
pnpm install
cd infra
npx cdk bootstrap # once per account/region
npx cdk deploy \
-c ownerBootstrap=you@example.com:fade-studio \
-c sesFromEmail=you@example.com # optional: SES sender (verify the email when asked)
# add -c sesIdentityExists=true if that email is already a verified SES identity
```
Requirements: AWS credentials, Docker (to build the web image), Bedrock access to Claude Haiku
4.5 in the region (default `us-east-1`). Outputs: `WebUrl` (dashboard + `/assistant`),
`McpEndpoint`, `McpDirectoryEndpoint` (register this one with the Alexa+ MCP Toolkit),
`CognitoDomain`, `AlexaClientId`. Sign up on the web URL with the `ownerBootstrap` email to manage
Fade Studio. `npx cdk destroy` removes it all (the database leaves a final snapshot).
### What it costs (us-east-1, on-demand, from the AWS Price List API, September 2026)
Always-on baseline, before any traffic:
| Resource | Price | Per month |
| -------------------------------------- | ----------------------------------------------- | ------------ |
| RDS PostgreSQL db.t4g.micro, single-AZ | $0.016/hour | $11.68 |
| RDS gp3 storage, 20 GB | $0.115/GB-month | $2.30 |
| NAT instance t4g.micro | $0.0084/hour | $6.13 |
| Public IPv4 address (NAT instance) | $0.005/hour | $3.65 |
| Secrets Manager, 2 secrets | $0.40/secret | $0.80 |
| Bedrock Knowledge Base index storage | $5.00/GB-month (policy documents are kilobytes) | ~$0.00 |
| CloudWatch Logs, ECR image, S3, SSM | small | < $1 |
| **Baseline** | | **≈ $25–26** |
Per use:
| What | Price | Example |
| --------------------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Claude Haiku 4.5 on Bedrock (US cross-region) | $1.10 / M input tokens, $5.50 / M output; cache reads $0.11 / M | One spoken turn ≈ 2–3 model calls ≈ 5–14k input tokens → **≈ $0.01–0.02**; a full booking conversation ≈ $0.05 |
| Amazon Location Service | Geocode (Core) $0.50 / 1,000; Search Text with contact details (Advanced) $1.50 / 1,000 | One "barber near Williamsburg" search ≈ $0.002 |
| Knowledge Base retrieval | $1.00 / 1,000 Retrieve calls | per FAQ question ≈ $0.001 |
| API Gateway HTTP API | $1.00 / million requests | negligible |
| Lambda (arm64) | $0.20 / million requests + $0.0000133 / GB-second | negligible at demo scale |
| Cognito Lite | first 10,000 monthly active users free, then $0.0055 / MAU | $0 for a demo |
| SES email | $0.16 / 1,000 emails (plan-dependent) | negligible |
A hackathon month with a few hundred test conversations comes to **roughly $25–35**. Deleting the
stack stops all charges except the final database snapshot (~$0.095/GB-month). The Knowledge Base
prices are from the Bedrock pricing page; everything else is from the AWS Price List API.
## Development
```sh
npm install -g pnpm@10 # or: corepack enable (needs admin rights on Windows)
pnpm install
cp .env.example .env
docker compose up -d # Postgres + migrations + seed
pnpm test # unit + integration tests (starts its own Postgres via Testcontainers)
pnpm lint && pnpm typecheck && pnpm format:check
pnpm db:migrate # apply migrations to DATABASE_URL
pnpm db:seed # seed DATABASE_URL (add -- --reset to reseed)
```
Integration tests need Docker. To use an existing Postgres instead, set `TEST_DATABASE_URL` to
an admin connection string (the user must be allowed to `CREATE DATABASE`).
## Repository layout
```
apps/
mcp-server/ # MCP server (+ owner REST API): Hono, MCP SDK v2, OAuth resource server
web/ # Next.js: /assistant (simulated Alexa+) and /dashboard (owners)
worker/ # Outbox delivery (email, SMS, Google Calendar) and maintenance sweeps
packages/
db/ # SQL migrations, migration runner, Kysely types, seed, test helpers
core/ # Domain logic: availability, holds, bookings, policies, store finder
notifications/ # SES / End User Messaging / Google Calendar clients, templates
shared/ # Zod schemas and types shared across apps
infra/ # AWS CDK stack
docs/
ARCHITECTURE.md # Components, request flows, data integrity, deployment
MCP_TOOLS.md # Every tool: description, inputs, outputs, examples, errors
DEMO_SCRIPT.md # The 3-minute demo, line by line
PRODUCT_FEEDBACK.md # Feedback on Bedrock and the other AWS services used
DECISIONS.md # Architecture decisions and deviations from the brief
FRICTION_LOG.md # Every tool/SDK/doc problem hit while building
```
## Data integrity
All instants are stored as `timestamptz` in UTC. Each business has an IANA time zone, validated
by a trigger, and working hours are local wall-clock times in that zone.
| Guarantee | Enforced by |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| No two confirmed bookings for the same staff member overlap | `EXCLUDE USING gist (staff_id WITH =, blocked_range WITH &&) WHERE (status = 'confirmed')` |
| No two active holds for the same staff member overlap | Same, on `holds`, `WHERE (status = 'active')` |
| Buffers after an appointment are respected | Constraints run on `blocked_range` = appointment + buffer |
| A hold becomes at most one booking | `UNIQUE (hold_id)` on `bookings` |
| Idempotency keys are unique per business | `UNIQUE (business_id, key)` |
| No cross-tenant references | Composite foreign keys on `(business_id, id)` |
Tested directly: 20 concurrent inserts for the same slot result in exactly one booking.
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues