Built for MIT HackNation Global AI Hackathon.
Otto isn't just a chatbot. It's an autonomous voice agent that lives in your browser, connects to your real Google Calendar, finds local businesses, and physically calls them to negotiate appointments on your behalf.
Booking sucks you have to:
- check hours against your calendar
- call and wait on hold
- negotiate a time that works
You tell Otto: "I popped my tire, and I need it fixed asap."
Otto does the rest
- Scans your calendar for free spots
- Finds highly rated mechanics near you
- Calls the office for you using Elevenlabs AI
- Negotiates a time slot
- Updates you with a confirmation and transcript
- Voice agent + outbound calling (ElevenLabs ConvAI → Twilio transport)
- dynamically generated call prompt per task/person
- live call status tracking
- transcript + summary displayed after completion
- Tool-using concierge
- pulls user context from MongoDB
- checks Google Calendar availability
- searches Google Places (nearby options)
- Production-style glue
- Google OAuth sign-in (Auth.js) + persistent sessions
- user profile stored in MongoDB
- Sign in with Google (
/login) - Fill profile (
/profile) — name/email, plus address/phone/about - Ask Otto to book something (home chat)
- Otto proposes a call plan → you click Initiate Call
- Watch the live status → see transcript + summary at the end
- Next.js 16 (App Router)
- Auth.js / NextAuth v5 (beta) + Google provider
- MongoDB
@auth/mongodb-adapterfor Auth.js persistencemongoosefor app profile data (lib/models/User.js)
- Tailwind CSS v4
- Google APIs
- Calendar API (read events)
- Places API (text search)
- ElevenLabs Conversational AI (signed session URL + outbound calling)
- Twilio (used by ElevenLabs outbound call flow)
- The UI chat (“Otto”) collects intent and context.
- Server actions + tool calls enrich the request:
actions/getUserContext.js(MongoDB)actions/getCalendarEvents.js(Google Calendar)lib/conciergeTools.js(Places search)
- When it’s time to act, the app calls:
POST /api/make-call→ creates an ElevenLabs outbound call with a task-specific promptGET /api/call-status?callId=...→ polls status + transcript
npm installcp .env.example .env.localFill in .env.local (see next section).
npm run dev- Auth.js
AUTH_SECRET(generate withnpx auth secret)AUTH_URL(recommended) e.g.http://localhost:3000
- Google OAuth
GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRET- Add redirect URI:
http://localhost:3000/api/auth/callback/google
- MongoDB
MONGODB_URI
- Google Calendar API (availability checks)
- requested via OAuth scope in
auth.config.ts
- requested via OAuth scope in
- Google Places API (nearby search)
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
These power the “call someone and get it done” part:
ELEVENLABS_API_KEYELEVENLABS_AGENT_ID(in-app conversational agent)ELEVENLABS_CALLER_AGENT_ID(outbound caller agent)ELEVENLABS_PHONE_IDPERSONAL_PHONE_NUMBER(the number to call)
npm run dev
npm run build
npm run start
npm run lintauth.ts/auth.config.ts— Auth.js config, scopes, routingapp/login— Google sign-inapp/profile— profile editor backed by MongoDBactions/*— server actions used as tools (context + calendar)lib/*— Mongo client, Mongoose model, concierge tool wrappersapp/api/elevenlabs/session— signed ConvAI session URLapp/api/make-call— creates outbound call (ElevenLabs)app/api/call-status— polls status + transcript
- Callback URL errors: set
AUTH_URLin.env.localand restart. - Turbopack/cache weirdness:
rm -rf .nextthen restart. - Calendar token missing: re-login and accept Calendar permissions.
MIT (or your preferred license)