A conversational meeting assistant for Google Calendar. Sign in with Descope, connect your Google Calendar through a Descope Outbound App, then chat with an AI agent that can read your agenda, check availability, and create, reschedule, or cancel events (with Google Meet links and email invites).
- Frontend — Next.js 16 (App Router) + React 19 + Tailwind CSS v4, Descope hosted auth flows.
- Backend — Express 5 API with a Mastra agent, Google Calendar tools, PostgreSQL for app data, and LibSQL/SQLite for agent memory.
- Auth & calendar access — Descope handles user authentication and the Google OAuth token lifecycle via an Outbound App; the backend never stores Google refresh tokens.
frontend (Next.js, :3000)
│ Bearer <Descope session JWT>
▼
backend (Express, :4000)
├─ requireSession ......... validates the Descope JWT, upserts the user
├─ /api/connections ....... starts / checks the Google Calendar connection
├─ /api/agent/chat ........ SSE stream from the Mastra agent
└─ /api/agent/threads ..... chat history (Mastra memory)
│
├─ Descope Management API .. fetch the user's Google access token
├─ Google Calendar API ..... events, free/busy, Meet links
└─ AI provider ............. Google Gemini (default) or OpenAI
Data stores:
| Store | Purpose |
|---|---|
| PostgreSQL | users, connections (calendar connection status) |
backend/mastra.db (SQLite) |
Mastra agent threads, messages, working memory |
- Node.js 20+
- Docker (for local PostgreSQL) — or an existing PostgreSQL instance
- A Descope project
- A Google Cloud project with the Google Calendar API enabled
- An AI provider key — Google Gemini (default) or OpenAI
- Create a Descope project and note the Project ID.
- Create a Management Key (Company Settings → Management Keys).
- Enable the
sign-up-or-inflow (used by the frontend sign-in screen). - Under Agentic Identity Hub → Connections, create a Google Calendar
connection:
- Authorization endpoint:
https://accounts.google.com/o/oauth2/v2/auth - Token endpoint:
https://oauth2.googleapis.com/token - Scope:
https://www.googleapis.com/auth/calendar - Client ID / secret: from your Google Cloud OAuth client
- Note the connection id (default expected by this app:
google-calendar)
- Authorization endpoint:
- Enable the Google Calendar API.
- Create an OAuth 2.0 Client ID (type: Web application).
- Add Descope's callback to Authorized redirect URIs:
https://api.descope.com/v1/outbound/oauth/callback(use the exact value shown in your Descope connection settings / region). - On the OAuth consent screen, while in Testing mode, add every Google account you will connect as a Test user.
cd backend
cp .env.example .env # fill in the values (see below)
npm install
docker compose up -d # starts PostgreSQL on port 5442 (from repo root)
npm run migrate # applies backend/sql/*.sql
npm run dev # http://localhost:4000backend/.env:
| Variable | Notes |
|---|---|
PORT |
API port (default 4000) |
APP_URL |
Frontend origin, used for CORS (default http://localhost:3000) |
DATABASE_URL |
PostgreSQL connection string |
DESCOPE_PROJECT_ID |
Descope project ID |
DESCOPE_MANAGEMENT_KEY |
Descope management key |
DESCOPE_CALENDAR_CONNECTION_ID |
Outbound connection id (default google-calendar) |
AI_PROVIDER |
google or openai |
AI_MODEL |
e.g. gemini-3.6-flash or gpt-4o-mini |
GOOGLE_GENERATIVE_AI_API_KEY |
required when AI_PROVIDER=google |
OPENAI_API_KEY |
required when AI_PROVIDER=openai |
ENABLE_MCP_SERVER |
true to mount the optional /mcp endpoint |
SERVER_URL, DESCOPE_MCP_SERVER_WELL_KNOWN_URL |
only needed when MCP is enabled |
cd frontend
cp .env.example .env
npm install
npm run dev # http://localhost:3000frontend/.env:
| Variable | Notes |
|---|---|
NEXT_PUBLIC_DESCOPE_PROJECT_ID |
same project ID as the backend |
NEXT_PUBLIC_API_URL |
backend base URL (default http://localhost:4000) |
- Open
http://localhost:3000and sign in via Descope. - In the sidebar Connections panel, click Connect and complete the Google consent screen. Use the ↻ button to refresh the connection status.
- Chat with the assistant, e.g.:
- "What's on today?"
- "Find a free slot tomorrow morning"
- "Create a 30-minute meeting tomorrow at 10am with alice@example.com"
- "Reschedule my 3pm to 4pm"
All routes require an Authorization: Bearer <Descope session token> header.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Service + database health |
GET |
/api/connections |
Current Google Calendar connection status |
POST |
/api/connections/connect |
Start the OAuth connect flow (returns a URL) |
POST |
/api/connections/refresh-status |
Re-check the connection against Descope |
GET |
/api/agent/threads |
List chat threads |
GET |
/api/agent/threads/:threadId |
Messages for a thread |
POST |
/api/agent/chat |
Server-sent event stream of the agent reply |
Agent tools: listUpcomingMeetings, checkCalendarBusy, createMeeting,
rescheduleMeeting, cancelMeeting.
backend/
src/
config/ Descope client, agent instructions, memory
db/ PostgreSQL pool
middleware/ requireSession (Descope JWT validation)
mcp/ optional MCP server + tools
repositories/ users, connections
routes/ connection + agent routes
services/ agent, calendar, connection, token services
sql/ migrations (run by npm run migrate)
frontend/
src/
app/ App Router pages (sign-in, dashboard)
components/ auth + dashboard UI
lib/ API client, agent + connection helpers
backend/mastra.db*and all.envfiles are git-ignored. Never commit real credentials — use the.env.examplefiles as templates.- While the Google OAuth consent screen is in Testing mode, Google expires the Calendar refresh token after 7 days, so the connection will periodically need to be re-established.