We recently launched the Calendrz MCP server — an open endpoint that lets any AI-powered app read your calendar availability and manage your mirroring preferences through the Model Context Protocol. Tools like Claude and ChatGPT already use it natively, but the real opportunity is for you to build something with it.
In this post we’ll walk through three practical app ideas and show you working code in both Python and Node.js. Every example connects to the Calendrz MCP server at https://app.calendrz.com/mcp using standard OAuth2 — no proprietary SDK needed.
How the Calendrz MCP server works
The Calendrz MCP server exposes your calendar data through a set of tools — structured functions that AI clients (or any HTTP client) can call. You authenticate via OAuth2, get a Bearer token, and then send JSON-RPC requests to the /mcp endpoint.
Here are the tools available today:
| Tool | What it does | Free tier? |
|---|---|---|
get_events |
Fetch calendar events across all connected accounts, with optional date filtering | Yes |
get_profile |
Get user profile, connected accounts, and subscription status | Yes |
get_mirror_preferences |
Read mirroring settings (import maybes, free time, visibility) | Yes |
get_tiers |
List all subscription tiers with features and pricing | Yes |
get_capabilities |
Discover which tools are available for the user’s tier | Yes |
save_mirror_preferences |
Update mirroring settings | Paid |
trigger_sync |
Force an immediate calendar sync across all accounts | Paid |
add_connected_account |
Get the URL to connect a new calendar account | Plus+ |
remove_connected_account |
Disconnect a calendar account | Plus+ |
Getting started: OAuth2 authentication
Before calling any tool, your app needs an access token. Calendrz supports Dynamic Client Registration (RFC 7591), so your app can register itself programmatically — no manual dashboard setup required.
The flow is:
- Register your client at
POST /oauth2/register - Redirect the user to
/oauth2/authorizefor consent - Exchange the authorization code for tokens at
POST /oauth2/token - Call the MCP endpoint with your Bearer token
You can also discover these endpoints automatically via the standard metadata URL:
GET https://app.calendrz.com/.well-known/oauth-authorization-server
Idea 1: Daily schedule briefing bot
Imagine a Slack bot or terminal script that runs every morning and gives you a plain-English summary of your day — across all your calendars. No more switching between Google Calendar tabs to piece together your schedule.
Python example
import httpx
from datetime import datetime, timedelta
CALENDRZ_MCP = "https://app.calendrz.com/mcp"
ACCESS_TOKEN = "your-oauth2-access-token"
def call_tool(tool_name: str, arguments: dict = None) -> dict:
"""Send a JSON-RPC request to the Calendrz MCP server."""
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments or {}
}
}
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/json"
}
resp = httpx.post(CALENDRZ_MCP, json=payload, headers=headers)
resp.raise_for_status()
return resp.json()
def morning_briefing():
today = datetime.utcnow().replace(hour=0, minute=0, second=0)
tomorrow = today + timedelta(days=1)
result = call_tool("get_events", {
"startDate": today.isoformat() + "Z",
"endDate": tomorrow.isoformat() + "Z"
})
# The result content is in result -> result -> content
content = result.get("result", {}).get("content", [{}])
events_data = content[0].get("text", "{}") if content else "{}"
import json
data = json.loads(events_data)
events = data.get("events", [])
if not events:
print("Your day is clear — no meetings!")
return
print(f"You have {len(events)} event(s) today:\n")
for ev in events:
start = ev.get("start", "")
title = ev.get("title", "(no title)")
account = ev.get("accountFriendlyName", "")
busy = ev.get("showTimeAs", "BUSY")
marker = "[BUSY]" if busy == "BUSY" else "[FREE]"
print(f" {marker} {start[:16]} {title} ({account})")
if __name__ == "__main__":
morning_briefing()
Run it as a cron job at 7 AM, pipe the output to a Slack webhook, and you have a daily briefing bot in under 50 lines.
Node.js example
const CALENDRZ_MCP = "https://app.calendrz.com/mcp";
const ACCESS_TOKEN = "your-oauth2-access-token";
async function callTool(toolName, args = {}) {
const resp = await fetch(CALENDRZ_MCP, {
method: "POST",
headers: {
"Authorization": `Bearer ${ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: { name: toolName, arguments: args },
}),
});
if (!resp.ok) throw new Error(`MCP error: ${resp.status}`);
return resp.json();
}
async function morningBriefing() {
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const result = await callTool("get_events", {
startDate: today.toISOString(),
endDate: tomorrow.toISOString(),
});
const content = result?.result?.content?.[0]?.text ?? "{}";
const { events = [] } = JSON.parse(content);
if (events.length === 0) {
console.log("Your day is clear!");
return;
}
console.log(`You have ${events.length} event(s) today:\n`);
for (const ev of events) {
const marker = ev.showTimeAs === "BUSY" ? "[BUSY]" : "[FREE]";
console.log(` ${marker} ${ev.start?.slice(0, 16)} ${ev.title} (${ev.accountFriendlyName})`);
}
}
morningBriefing();
Idea 2: “Find me a free slot” API
Building a scheduling product? A “book a meeting with me” link? You need to know when someone is actually free — across all their calendars, not just one. Calendrz already aggregates availability from every connected account. Your app just needs to read it and find the gaps.
Python example
from datetime import datetime, timedelta
import json
import httpx
CALENDRZ_MCP = "https://app.calendrz.com/mcp"
ACCESS_TOKEN = "your-oauth2-access-token"
def call_tool(tool_name, arguments=None):
payload = {
"jsonrpc": "2.0", "id": 1,
"method": "tools/call",
"params": {"name": tool_name, "arguments": arguments or {}}
}
resp = httpx.post(CALENDRZ_MCP, json=payload,
headers={"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/json"})
resp.raise_for_status()
content = resp.json().get("result", {}).get("content", [{}])
return json.loads(content[0].get("text", "{}")) if content else {}
def find_free_slots(date, work_start=9, work_end=17, slot_minutes=30):
"""Find free slots on a given date across all connected calendars."""
start = date.replace(hour=0, minute=0, second=0)
end = start + timedelta(days=1)
data = call_tool("get_events", {
"startDate": start.isoformat() + "Z",
"endDate": end.isoformat() + "Z"
})
# Collect busy intervals (only BUSY events, skip all-day and FREE)
busy = []
for ev in data.get("events", []):
if ev.get("allDay") or ev.get("showTimeAs") != "BUSY":
continue
ev_start = datetime.fromisoformat(ev["start"].replace("Z", "+00:00"))
ev_end = datetime.fromisoformat(ev["end"].replace("Z", "+00:00"))
busy.append((ev_start, ev_end))
# Merge overlapping intervals
busy.sort()
merged = []
for s, e in busy:
if merged and s <= merged[-1][1]:
merged[-1] = (merged[-1][0], max(merged[-1][1], e))
else:
merged.append((s, e))
# Walk through work hours and find gaps
slots = []
cursor = date.replace(hour=work_start, minute=0, second=0)
day_end = date.replace(hour=work_end, minute=0, second=0)
slot_delta = timedelta(minutes=slot_minutes)
while cursor + slot_delta <= day_end:
slot_end = cursor + slot_delta
is_free = all(slot_end <= bs or cursor >= be for bs, be in merged)
if is_free:
slots.append((cursor, slot_end))
cursor += slot_delta
return slots
# Usage
tomorrow = datetime.utcnow().replace(hour=0, minute=0) + timedelta(days=1)
free = find_free_slots(tomorrow, work_start=9, work_end=17, slot_minutes=30)
print(f"Free 30-min slots for {tomorrow.date()}:")
for start, end in free:
print(f" {start.strftime('%H:%M')} - {end.strftime('%H:%M')}")
Wrap this in a Flask or FastAPI endpoint and you have a self-hosted scheduling API that respects all your calendars — without giving a third-party service access to your event details.
Idea 3: Multi-calendar dashboard widget
If you run a team dashboard (Grafana, Notion embed, internal tool), you could build a widget that shows a unified view of the day. Here’s a Node.js snippet that fetches events and groups them by connected account — ready to render in any frontend framework.
Node.js example
async function getDashboardData(accessToken) {
const callTool = async (name, args = {}) => {
const resp = await fetch("https://app.calendrz.com/mcp", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: { name, arguments: args },
}),
});
const json = await resp.json();
return JSON.parse(json?.result?.content?.[0]?.text ?? "{}");
};
// Fetch profile and today's events in parallel
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const [profile, eventsData] = await Promise.all([
callTool("get_profile"),
callTool("get_events", {
startDate: today.toISOString(),
endDate: tomorrow.toISOString(),
}),
]);
// Group events by connected account
const byAccount = {};
for (const ev of eventsData.events ?? []) {
const key = ev.accountFriendlyName || "Unknown";
if (!byAccount[key]) byAccount[key] = [];
byAccount[key].push({
time: ev.start?.slice(11, 16),
title: ev.title,
busy: ev.showTimeAs === "BUSY",
});
}
return {
user: profile.firstName,
tier: profile.tier?.description,
accounts: profile.connectedAccounts?.length ?? 0,
mirrorActive: !profile.mirrorDisabled,
schedule: byAccount,
totalEvents: eventsData.count ?? 0,
};
}
// Example output:
// {
// user: "Liviu",
// tier: "Professional",
// accounts: 3,
// mirrorActive: true,
// schedule: {
// "Work (Google)": [
// { time: "09:00", title: "Standup", busy: true },
// { time: "14:00", title: "1:1 with Sarah", busy: true }
// ],
// "Personal (Gmail)": [
// { time: "18:00", title: "Dentist", busy: true }
// ]
// },
// totalEvents: 3
// }
Getting your OAuth2 token
All the examples above assume you have an access token. Here is how to get one using Dynamic Client Registration — your app registers itself, then walks the user through the standard OAuth2 authorization code flow.
Python: full OAuth2 flow
import httpx
import secrets
import webbrowser
from urllib.parse import urlencode, urlparse, parse_qs
from http.server import HTTPServer, BaseHTTPRequestHandler
CALENDRZ = "https://app.calendrz.com"
REDIRECT_URI = "http://localhost:8919/callback"
# Step 1: Register your client (one-time)
reg = httpx.post(f"{CALENDRZ}/oauth2/register", json={
"client_name": "My Calendar App",
"redirect_uris": [REDIRECT_URI],
"grant_types": ["authorization_code", "refresh_token"],
"scope": "openid profile email",
"token_endpoint_auth_method": "client_secret_post"
}).json()
client_id = reg["client_id"]
client_secret = reg["client_secret"]
print(f"Registered client: {client_id}")
# Step 2: Build authorization URL and open browser
state = secrets.token_urlsafe(32)
auth_url = f"{CALENDRZ}/oauth2/authorize?" + urlencode({
"response_type": "code",
"client_id": client_id,
"redirect_uri": REDIRECT_URI,
"scope": "openid profile email",
"state": state
})
auth_code = None
class CallbackHandler(BaseHTTPRequestHandler):
def do_GET(self):
nonlocal auth_code
params = parse_qs(urlparse(self.path).query)
auth_code = params.get("code", [None])[0]
self.send_response(200)
self.end_headers()
self.wfile.write(b"Done! You can close this tab.")
def log_message(self, *args):
pass # Suppress request logs
print("Opening browser for authorization...")
webbrowser.open(auth_url)
server = HTTPServer(("localhost", 8919), CallbackHandler)
server.handle_request() # Wait for one callback
# Step 3: Exchange code for tokens
tokens = httpx.post(f"{CALENDRZ}/oauth2/token", data={
"grant_type": "authorization_code",
"code": auth_code,
"redirect_uri": REDIRECT_URI,
"client_id": client_id,
"client_secret": client_secret
}).json()
print(f"Access token: {tokens['access_token'][:20]}...")
print(f"Refresh token: {tokens.get('refresh_token', 'N/A')[:20]}...")
print(f"Expires in: {tokens.get('expires_in')} seconds")
What will you build?
The Calendrz MCP server gives you a clean, authenticated API to calendar availability across all of a user’s accounts — Google, Microsoft, and more. Whether you’re building a scheduling tool, a productivity dashboard, a morning briefing bot, or something we haven’t thought of yet, the building blocks are there.
A few more ideas to get you thinking:
- Meeting prep assistant — Pull today’s events and auto-generate briefing notes for each meeting by cross-referencing attendees with your CRM
- Focus time protector — Monitor your calendar and alert you (or your team) when your deep-work blocks are being encroached on
- Time audit report — Aggregate a week’s worth of events across all calendars and generate a breakdown of where your time went
- Smart auto-reply — When someone emails asking “are you free Tuesday?”, an agent could check your Calendrz availability and draft a reply with actual open slots
Get started at calendrz.com/mcp and let us know what you build.







