Skylight MCP Server
Summary: This MCP server gives Claude read/write access to a Skylight family frame for calendar events, chores, lists, messages, family info, and week planning.
Read calendar events and connected calendar sources; create, update, and delete events.
Read, filter, create, update, and delete chores.
Read lists and items; create, update, and delete lists and list items; add or check off items.
Add tasks to the task box.
Read family members, frame/household info, devices, avatars, and colors.
Read, send, and edit messages shown on the family frame; sends are preview-first and require explicit confirmation.
Get a week overview, find calendar conflicts, find free time slots, and bulk-plan meals with dry-run and optional grocery-list push (Plus required for meal planning).
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Skylight MCP Server What's on our family calendar today?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Skylight MCP Server
An MCP (Model Context Protocol) server for the Skylight Calendar. Lets Claude (including Claude Duet, via the hosted mode) read and plan against your family calendar, chores, lists, meals and rewards.
Unofficial. This project is not affiliated with or endorsed by Skylight. Skylight has no public API; this talks to the same private endpoints their own apps use, discovered by observing app traffic. It can stop working or be blocked at any time, without notice. Use it with your own account, at your own risk, and keep request volume modest.
Sign-in reuses the Skylight mobile app's own OAuth flow: it presents the app's client ID (
skylight-mobile) and redirect, because Skylight issues no client of its own to third parties. Skylight's Terms of Service prohibit access by means other than its supported interfaces and reverse engineering (§5.4), and let Skylight suspend accounts with or without cause (§16). Using this project is a risk to the Skylight account you sign in with, not only to the integration.Forked from TheEagleByte/skylight-mcp (MIT). The sign-in and credential-store design follows kamilio/skylight-calendar-agent (MIT). See
THIRD_PARTY_NOTICES.md.
Features
Calendar: read events and connected calendars; create, update and delete events
Planning: one-call week overview, overlap detection, free-slot search, bulk meal planning
Meals (Plus): see, add, rename, move and delete scheduled meals; browse and edit recipes
Messages: sending to specific devices, scheduling, repeating, voice notes, editing a message that was sent to specific profiles (the app's edit form does not allow changing recipients), and read-aloud (the option exists on
send_messagebut was not tested live).Chores and tasks: read, create, update and delete chores; add to the task box
Lists: read, create, update and delete lists and items
Family: read profiles, devices and household info
Rewards (Plus): read points and rewards; create, update, redeem and delete
See What is supported for what is tested, what is not, and what the hosted server leaves out.
Related MCP server: skylight-mcp
Quick Start
You need Node 20+, a Skylight account, and an MCP client (Claude desktop, Claude
Code, or any other). Nothing to clone or build. Run the npx commands from any
normal folder, not from inside a clone of this repo (see Troubleshooting).
Do I need the hosted server? Probably not. The steps below give you the
local server: it runs on your own computer while your MCP client is open and
exposes every tool. The hosted server (a Cloudflare Worker) is optional. You
only need it if Claude Duet or Claude on another device has to reach Skylight
when this computer is off. It means cloning the repo, a free Cloudflare
account, npx wrangler login, npm run worker:setup, and a second sign-in,
and it exposes read and write tools plus a short list of deletes (chores, lists, list items, rewards, recipes, meal sittings, messages; not calendar events). Do the local
setup first; see
Hosted server (Claude Duet, Claude on other devices)
if you need it later.
1. Run the setup
npx -y @davidwoods/skylight-mcp initinit asks for your timezone (it offers the one your computer uses), runs the
browser sign-in, finds your frame from your account, saves the frame ID and
timezone to a small settings file, runs doctor, and prints the lines to paste
into your MCP client. If your account has more than one frame it asks which;
if it cannot list frames it asks for the number from the address bar
(https://ourskylight.com/my-skylight/<number>; see
Finding your Frame ID).
The sign-in works like this: init prints a Skylight URL and offers to open it:
press Enter and your browser opens it (or open the URL yourself). Sign in there. The browser bounces through
https://ourskylight.com/welcome?code=... and on to the home page too fast to
copy. Get the link from your browser history: Cmd-Y (Mac) / Ctrl-H (PC),
search welcome?code, right-click the Skylight result dated today (top of the
list; older ones are earlier sign-ins and will not work), Copy link address,
and paste it when init asks. Your password never touches this program; only
the resulting OAuth tokens are stored, encrypted with a key in the macOS
Keychain (plain 0600 file on Linux/Windows). macOS may ask once to allow
Keychain access. If the Keychain is unusable, init stops; it never falls back
to plaintext on macOS.
The settings file holds the frame ID and timezone only: no token, no host.
It lives in ~/Library/Application Support/skylight-mcp/config.json (macOS),
%APPDATA%\skylight-mcp\config.json (Windows) or
~/.config/skylight-mcp/config.json (elsewhere), mode 0600, and an
unrecognised key makes the server refuse to start. Environment variables
(SKYLIGHT_FRAME_ID, SKYLIGHT_TIMEZONE) override it. Run init again to
change either value.
2. Add to your MCP client
Claude desktop (Settings → Desktop app → Local MCP servers → Edit config; older versions: Settings → Developer → Edit Config), or any client that uses mcp.json:
{
"mcpServers": {
"skylight": {
"command": "npx",
"args": ["-y", "@davidwoods/skylight-mcp"]
}
}
}Claude Code:
claude mcp add skylight -- npx -y @davidwoods/skylight-mcpRestart the client fully (Cmd-Q on Mac), then ask it "what's on the family calendar this week?"
3. Check it any time
npx -y @davidwoods/skylight-mcp doctordoctor checks Node, the frame ID, the timezone, your sign-in and one real call
to Skylight, and says what to fix. It never prints tokens or your frame ID.
Add --offline to skip the network call.
What you can sync, and what this server can do
Skylight's Sync Calendar screen offers these. From the app's screen; the "this server" column is what changes here.
Option in Skylight | Add it | Manage it with this server |
Calendar URL (team, school, league, iCloud published calendar) |
| Rename, link to profiles, remove, list |
Google, Outlook/Hotmail/Live, iCloud account, Cozi, TeamSnap | Sign in inside the Skylight app (cannot be done here) | List only |
US Holidays, Individual Events | In the app | List only |
Calendars from a subscription link are one-way: Skylight reads them and cannot write back.
Add a team, school or league calendar
npx -y @davidwoods/skylight-mcp calendars addPaste the calendar's address when asked (the link ending in .ics, or starting with
webcal://). Nothing is shown as you paste, and the address never goes into your
shell history, a chat, or a log: it is a secret, because anyone with the link can
read that calendar. Skylight fetches the feed itself, names the calendar from it, and
the events appear on your frame. This is a command and deliberately not an MCP tool.
To stop syncing one, ask your assistant to remove it (delete_source_calendar, which
previews first), or remove it in the Skylight app. Google, Outlook, iCloud and Cozi
accounts are connected in the Skylight app, not here.
Manual setup (without init)
If you would rather not use init, or it fails, set the two values yourself
and sign in with the lower-level commands. Find your frame ID as above; the
timezone is an IANA name such as America/Chicago and must be set: it
defaults to America/New_York, and event times come out wrong anywhere else.
# macOS / Linux
export SKYLIGHT_FRAME_ID=your_frame_id
export SKYLIGHT_TIMEZONE=America/New_York
# Windows PowerShell: $env:SKYLIGHT_FRAME_ID="your_frame_id"; $env:SKYLIGHT_TIMEZONE="America/New_York"
npx -y @davidwoods/skylight-mcp auth login
npx -y @davidwoods/skylight-mcp auth complete # then paste the welcome?code= linkTerminal variables are not inherited by your MCP client. Put them in the
client's env block instead:
"env": {"SKYLIGHT_FRAME_ID": "your_frame_id", "SKYLIGHT_TIMEZONE": "America/New_York"}
(Claude Code: -e SKYLIGHT_FRAME_ID=... -e SKYLIGHT_TIMEZONE=...).
auth status and auth logout do what they say.
From source (contributors)
git clone https://github.com/dwooods/skylight-mcp.git
cd skylight-mcp && npm install && npm run build
node dist/index.js auth login # same steps as above, using node dist/index.jsIn the client config use "command": "node", "args": ["/path/to/skylight-mcp/dist/index.js"].
Windows (partly verified)
Verified on one Windows machine (Node 24): npx runs the package, doctor --offline
works, auth login and auth complete finish the real sign-in, the credential is
saved to %APPDATA%\skylight-mcp\credential.json, and the Claude desktop app runs the
server from the config below and reads real calendar data. Not yet verified:
doctor's Skylight check on Windows (it was broken on every platform in 1.7.0 and is
fixed in 1.8.0), the live test suite, and worker:setup.
npxis a.cmdfile on Windows, so in a client config use"command": "cmd", "args": ["/c", "npx", "-y", "@davidwoods/skylight-mcp"].Set variables in PowerShell like this, then run the command on the next line:
$env:SKYLIGHT_FRAME_ID="your_frame_id"; $env:SKYLIGHT_TIMEZONE="America/New_York".In Windows Terminal you can hold Ctrl and click the sign-in URL. In the history search, right-click the result dated today and choose Copy link address.
The sign-in is stored unencrypted in
%APPDATA%\skylight-mcp\credential.json. There is no Keychain equivalent wired in, and Windows ignores Unix0600permissions. Only your Windows user profile protects it.
Troubleshooting
Symptom | Likely cause and fix |
| You ran |
Server fails to start, mentions configuration | No frame ID: run |
"Not signed in" | Run |
macOS Keychain error | The server refuses to fall back to plaintext. Unlock the Keychain or allow access when prompted. |
Events show at the wrong hour |
|
Tools missing after an update | Fully quit and reopen the client; it caches the tool list. For a hosted connector use its "Refresh tools list". |
Worked yesterday, fails today | Skylight has no public API and may have changed. Run |
First five things to ask Claude
Once doctor is clean, try these in order. The first three only read. The last two show how the write guards work, so you see them before anything lands on your Skylight display.
"What's on our family calendar this week?" Reads events (
get_calendar_events). If times are off by hours, yourSKYLIGHT_TIMEZONEis wrong."Give me an overview of the week and point out any conflicts." One call that merges events, chores, meals and lists (
get_week_overview,find_conflicts)."When are we all free for 90 minutes on Saturday?" Finds gaps around the fixed events (
find_free_slots)."Plan dinners for Monday to Thursday, but do a dry run first." Shows what would be written and which slots are already filled (
plan_week_meals). Nothing is written until you say so, and filled slots are never overwritten. Needs Skylight Plus."Draft a message to everyone saying dinner is at 6, but don't send it yet." The send tool previews the exact text and posts nothing until Claude is told to confirm. Messages show on your display, so say yes only when you mean it.
If a tool you expected is missing, see the Troubleshooting table, and check "What is supported" for tools that are local-only or Plus-only.
Instructions for AI
Copy this into your AI's custom instructions or system prompt:
You have access to the Skylight MCP server. Skylight is a smart family calendar display that shows calendars, chores, grocery lists, meals, and rewards. Use the Skylight tools to help manage family schedules and organization.
Tips:
Call
get_family_membersbefore assigning chores to get member namesGrocery items default to the main grocery list if no list specified
Dates accept "today", "tomorrow", day names, or YYYY-MM-DD format
Some tools (rewards, meals, photos) require Skylight Plus subscription
Prerequisites
Node.js 20+
A Skylight account with an active subscription
Your Skylight Frame ID (see Finding your Frame ID)
Authentication
Sources are tried in this order:
SKYLIGHT_TOKEN- an explicit token in the environment, if set.Stored OAuth credential (recommended) - created by
skylight-mcp auth login/auth complete. The access token is refreshed automatically; refresh tokens rotate and the rotated token is persisted.SKYLIGHT_EMAIL+SKYLIGHT_PASSWORD(deprecated) - the server replays Skylight's web login form itself. This works but puts your password in your MCP client config in plaintext. Prefer the browser sign-in above.
If none is present the server fails with instructions rather than starting.
The browser sign-in is the mobile app's flow, not one Skylight offers to third
parties: the server opens Skylight's own sign-in page with the app's client ID
(skylight-mobile), PKCE (S256) and a state check, and you paste back the
ourskylight.com/welcome address it lands on. Your password never reaches this
project. Skylight could change or block this flow at any time.
Where the credential lives:
Platform | Location | Protection |
macOS |
| AES-256-GCM; key in Keychain ( |
Linux / other |
|
|
Windows |
| Not encrypted. Windows ignores |
If the Keychain is unavailable on macOS the server refuses to read or write the credential; it never silently downgrades to plaintext.
Finding your Frame ID
The frame ID is the household identifier. The easy way: after signing in, the
Skylight web app lands on https://ourskylight.com/my-skylight/<number>. That
number is your frame ID. Treat it as a secret.
The hard way, if that URL doesn't appear:
Capture any API request from the Skylight app
Look at the URL path:
/api/frames/{frameId}/...Example:
/api/frames/abc123/chores→ frame ID isabc123
Configuration
Variable | Required | Description |
| No | Explicit API token; overrides the stored credential |
| No |
|
| No (deprecated) | Fallback email/password login |
| No (deprecated) | Fallback email/password login |
| Yes, unless saved by | Your household frame ID; overrides the settings file |
| No | Default timezone; overrides the settings file (default: |
Auth Notes
The API host is pinned to
https://app.ourskylight.com. No environment variable or.envfile can redirect requests (and the bearer token) elsewhere.auth logoutdeletes the local credential only; Skylight has no public way to revoke a session, so change your password if a credential may have leaked.Skylight has no official API. This uses the same OAuth client the Skylight app uses, and it may break or be blocked at any time.
Hosted server (Claude Duet, Claude on other devices)
The same code also runs as a remote MCP server on Cloudflare Workers, which is how Claude Duet (and any Claude client that can't reach your Mac) connects. One Worker is both the OAuth authorization server Claude talks to and the MCP resource server. Signing in is the same browser-paste flow as the CLI; your Skylight password never reaches the Worker, and the Skylight tokens are kept encrypted in the OAuth grant, not in a database you manage.
The hosted server can change and delete things other people own. Whoever (or whatever) holds the
connection can create, edit and delete anything in the connected Skylight frame, including events
on every Google calendar connected to it, not only the shared family one. That includes another
person's personal Google calendar if it is connected to Skylight: a delete or edit there is made at
Google, shows on their phone, and cannot be undone from this server. Google records the change
as made by the account that is connected to Skylight, not by you or by the AI. Deleting a repeating event
with scope all removes the whole series, past and future. Claude Duet does not ask before a
destructive tool call, and the preview step is a parameter the model sets, not something the server
enforces. Limits that do hold: 60 changes an hour, at most 10 of them deletes, and events from
subscribed feeds are read-only. Check which calendars your frame syncs (get_source_calendars)
before you connect a client you do not supervise, and remove delete_calendar_event from
HOSTED_DESTRUCTIVE_ALLOWLIST if that is more access than you want to give.
Which tools the hosted server exposes, and why
The hosted server registers all read and create/update tools, plus these
deletes: delete_chore, delete_list, delete_list_item, delete_reward,
delete_recipe, delete_meal_sitting, delete_task_box_item, delete_message and
delete_calendar_event (events created in Skylight or on a Google calendar, and repeating ones with an explicit scope; never subscribed feeds). It does not
register delete_source_calendar, delete_label or photo uploads. The local (stdio) server
registers everything. This is a deliberate choice, not a limitation of the Worker:
The hosted server is what Claude Duet drives proactively, sometimes unattended. Every allowlisted delete previews and deletes nothing unless
confirm: true. That flag is set by the model, not by a person: the server cannot tell whether anyone said yes, and some clients do not prompt before a tool runs. The model is meant to show the exact target and wait for a yes, and it can be told to in its instructions, but nothing here enforces it. The server's hard limits are the write limiter (60 changes an hour, at most 10 deletes) and the fixed allowlist.delete_source_calendar(removing a synced calendar) and adding a feed stay local-only: a feed address is a secret and a wider, rarer change than deleting one event.Photo tools are uploads the Worker has no reason to carry.
The allowlist is one list, HOSTED_DESTRUCTIVE_ALLOWLIST in
src/worker/mcp.ts. Add or remove a tool name there and redeploy. For full
parity set HOSTED_EFFECTS to ["read", "write", "destructive"] and
photos: true in the same file. Every tool's classification lives in
src/tools/registry.ts, so you can also carve out a profile of your own
(read-only, for example, is ["read"]).
If you run both servers in the Claude desktop app, Claude sees two sets of mostly duplicate tools and may use either for writes. Disable the local one in Settings → Connectors when you do not need calendar deletes or photo tools, or say "use the local Skylight server" when you do.
Deploy (once)
You need a free Cloudflare account. From a clone of this repo:
npm install
npx wrangler login
npm run worker:setup # add -- --dry-run first to see the stepsworker:setup creates your KV namespace, writes a gitignored
wrangler.local.jsonc with your real ID (the committed wrangler.jsonc keeps a
placeholder, so nothing account-specific can be committed), asks you for a
sign-in passphrase, deploys, sets WORKER_URL, deploys again and prints the
connector URL. Re-deploy later with npm run worker:deploy:local.
Upgrading a Worker deployed before 1.13.1: add the change counter to your
wrangler.local.jsonc (a new setup gets it automatically), then deploy:
"durable_objects": { "bindings": [{ "name": "WRITE_LIMITER", "class_name": "WriteLimiter" }] },
"migrations": [{ "tag": "v1-write-limiter", "new_sqlite_classes": ["WriteLimiter"] }],Without it the Worker still runs, but its change limit counts in memory and
limits nothing across requests. After a deploy, make a change and look for
"limiter":"durable" in the Worker's logs.
Doing it by hand instead:
cp wrangler.jsonc wrangler.local.jsonc # gitignored: your real IDs live only here
npx wrangler kv namespace create OAUTH_KV # paste the id into wrangler.local.jsonc, not wrangler.jsonc
npx wrangler secret put SETUP_PASSPHRASE -c wrangler.local.jsonc # 12+ chars; required to link an account
npm run worker:deploy:local # prints the Worker's URL
npx wrangler secret put WORKER_URL -c wrangler.local.jsonc # paste that URL (origin only, no path)
npm run worker:deploy:localWORKER_URL must be exactly the public origin of the Worker; every token is
bound to it. It is a secret rather than a vars entry so the server's
address is not published with the source.
Connect from Claude
In Claude (or Duet) → Settings → Connectors → Add custom connector:
Name:
SkylightURL:
https://skylight-mcp.<your-subdomain>.workers.dev/mcp
Claude opens the consent page. Enter the setup passphrase, your frame ID and timezone, then follow the two-step Skylight sign-in and paste the redirect URL. Connectors are shared across your Claude account, so Duet sees it too.
The server also serves /setup (these steps, for the host it runs on) and
/version (just the version number, handy for confirming a redeploy took).
What is stored where
Thing | Where | Protection |
Skylight access + refresh tokens | OAuth grant props in | Encrypted by workers-oauth-provider; the key is derived from the token Claude holds, so KV alone is not enough |
PKCE verifier during sign-in | Upstream transaction in | Bound to your browser by a |
Setup passphrase | Worker secret | Never in the repo or |
Nothing else | The Worker has no database and no logs of tokens |
To revoke: remove the connector in Claude (revokes the grant), or
npx wrangler kv key list --binding OAUTH_KV and delete, or change your
Skylight password.
What is supported
Status vocabulary, used honestly:
Live-tested: run against a real Skylight frame by this project, with the result checked by reading back.
Inherited: came from the upstream project and works there, but this project has not re-run it live.
Mocked only: covered by this project's mocked tests; not yet run against real data.
Skylight has no public API. Everything below can break when Skylight ships a web release, which happens without notice. The web app version and API version last captured are recorded in the repository's build log.
Area | What works | Hosted server (Claude Duet) | Status |
Calendar | Read events and source calendars; create (with profiles), update, delete events | All, delete included (preview first; never subscribed feed events). Removing a synced calendar or a label is local only | Read, create, update and delete live-tested, including repeating events (edit scopes one and all; delete scopes one, future and all), all-day series and a one-off event written to a Google calendar. Not yet live: a series written to Google |
Planning | Week overview, conflicts, free slots, bulk meal plan | All four | All four live-tested (conflicts and free slots as reads; the bulk meal plan through the hosted server) |
Meals (Plus) | Read categories and meals; create, rename, move, delete one-off meals | All, delete included | Live-tested |
Recipes (Plus) | Read, create, update, delete recipes; push ingredients to the grocery list | All, delete included | Inherited |
Messages | Read; post to everyone or to named profiles (silent by default); edit text and sender (everyone-messages only); delete | All four, delete included | Live-tested |
Chores and routines | Read (routines marked, Up for Grabs included); create, update, delete chores; mark one occurrence of a routine or recurring chore done or not done | All, delete included | Read, create and delete live-tested; completion captured live, tool not yet run live; the rest inherited |
Task box | List items; add a task; delete a template by id | Yes, delete included | List and delete live-tested; add inherited |
Lists | Read lists and items; create, update, delete lists and items | All, delete included | Inherited |
Family and devices | Profiles with IDs, avatars, colours, household info, devices | Yes | Profiles live-tested; the rest inherited |
Rewards (Plus) | Read points (by profile) and rewards; create, update, redeem, unredeem, delete | All, delete included | Points read, create and delete live-tested; the rest inherited |
Photos (Plus) | List albums | No | Inherited |
Not supported
Nothing here is hidden behind a flag. It is either unavailable from Skylight or not yet captured.
Messages: sending to specific devices, scheduling, repeating, voice notes, and read-aloud (the option exists on
send_messagebut was not tested live). Deleting works but the app has no delete button.Meals: editing a repeating meal (refused), repeat rules other than weekly, deleting a meal that was edited on its own after being in a series (refused), saving a meal to the recipe box from the edit form, and Skylight's own "Plan Meals" feature.
Task box: editing or completing tasks, and saving a routine template to it.
Chores: completing a one-off (non-recurring) chore, Skip, daily or monthly routine schedules (only the weekly format has been captured), and editing a recurring chore's date, time, assignee or stars. Un-completing a routine cannot reduce its
longest_streak. Whether "this and future" from a later day keeps the earlier days has not been checked (the capture started from the first day).Profiles: creating or editing them. Read-only.
Settings, account, notifications, sleep schedule, device navigation: none of these are exposed.
Photos: uploads and anything beyond listing albums.
Accounts with more than one frame: the server is pinned to one frame ID.
Calendar feeds: adding one is a CLI command,
skylight-mcp calendars add, and deliberately not an MCP tool, because the feed address is a secret and a tool argument would put it in the chat transcript. You paste it into a hidden prompt; only the calendar's name and host are printed. Linking a feed to a profile or label, changing which calendar new events are saved to, and adding Google, Outlook, iCloud or Cozi accounts (browser sign-in in the Skylight app) are not supported.Credentials: sign-in, sign-out and token access are CLI commands (
skylight-mcp auth ...) and are deliberately not MCP tools.
Keeping this honest
Every tool must appear in the tables below with the effect it really has, and a test fails if one is missing or mislabelled. When you add, change or remove a tool, update this section in the same change.
Available Tools
Effects: read changes nothing; write creates or edits and can be edited
again; destructive cannot be undone from the API. The hosted server registers
read and write tools plus a fixed list of deletes, each preview-first (see "Which tools the hosted server exposes, and why"); delete_source_calendar and delete_label exist on the local server alone.
Every tool refuses an argument it does not know and names it, so a misspelt argument is an error, never a call that quietly ran with defaults.
Planning Tools
Built for "what does the week look like and what is for dinner" questions. One call returns calendar, chores/tasks, meals and lists, merged per day.
Tool | Effect | Description |
| read | Per-day events, chores/tasks (status, assignee, points), meals, overdue chores, conflicts and list summaries in one compact call. Omits owner emails and calendar IDs. |
| read | Timed events that overlap in a date range. All-day events never conflict. A subscribed feed that lists the same event twice (same start, end and title apart from a trailing note such as "Arrive 20 minutes early") is reported separately as a duplicate, not a conflict. |
| read | Gaps of at least N minutes inside a daily window (default 08:00 to 21:00). |
| write | Fill several meal slots at once. |
Times are computed in the frame's time zone, including DST changes.
Message Tools
Messages are the notes shown on the frame (Skylight's web app calls them "nudges").
Tool | Effect | Description |
| read | Messages in a date range with time, text, recipients and sender. Never returns the presigned audio URLs. |
| write | Post a message to the frame now, on all devices. Previews by default and posts only with |
| write | Edit the text (and optionally the sender) of an existing message. Only messages sent to everyone on all devices that don't repeat; others must be edited in the app. |
| destructive | Delete a message. The app has no delete button; this uses |
Calendar Tools
Tool | Effect | Description |
| read | Events for a date range |
| read | Connected calendar sources (Google, iCloud, etc.), with each one's role and whether it is editable. Private feed addresses are hidden: only the host is shown |
| destructive | Stop syncing a subscribed calendar feed (a team, school or league schedule). Preview first, then |
| write | Link a subscribed calendar feed to family profiles, or unlink it (the "Link profiles" checkboxes). Preview first, then |
| write | Rename a subscribed calendar feed as it appears in Skylight (feeds are named from the feed itself, often at length). Preview first, then |
| write | Add an event. |
| write | Change an event, including who it is assigned to ( |
| destructive | Delete one event, or part of a repeating one. Needs the event's |
Chore Tools
Tool | Effect | Description |
| read | Chores with their ids, with optional filters (date, assignee, status) |
| write | Create a one-off chore for one family member (assignee required; Skylight rejects a chore with no profile). Repeating tasks: |
| write | Rename, reschedule, reassign a one-off chore. For recurring chores and routines only a rename and a weekly schedule ( |
| write | Create a weekly routine (morning, afternoon or evening) for one or more profiles, via |
| write | Mark one occurrence of a routine or recurring chore done, or undo it. Previews unless |
| destructive | Delete a chore. Previews unless |
Task Tools
Tool | Effect | Description |
| read | List the task box: reusable chore and routine templates (no assignee or date) |
| write | Add a template to the task box |
| destructive | Delete one Task Box template by ID (shown by |
List Tools
Tool | Effect | Description |
| read | All lists |
| read | Items in one list |
| write | Create a list |
| write | Rename or change a list |
| write | Add an item |
| write | Edit or tick off an item |
| destructive | Delete a list. Local server only. |
| destructive | Delete an item. Local server only. |
Family Tools
Tool | Effect | Description |
| read | Family member profiles, with the IDs and names that |
| read | Household/frame information |
| read | Skylight devices |
| read | Calendar labels (team, school year, holiday feed: the groupings that are not family members), each with its count of upcoming events and whether a synced calendar still writes to it, so unused ones are easy to spot |
| destructive | Delete one calendar label (the iOS app can do this; the web app cannot). Preview first, then |
| read | Available profile avatars |
| read | Available profile colours |
Meal Tools
Meals and recipes need Skylight Plus.
Tool | Effect | Description |
| read | Breakfast, Lunch, Dinner, Snack and their IDs |
| read | Scheduled meals in a date range, with sitting IDs; |
| write | Schedule a meal on a date (free text or a saved recipe). |
| write | Rename, re-categorise, or set the note or the instructions ( |
| destructive | Delete a meal. For a repeating meal, |
| read | Browse saved recipes |
| write | Add or change a recipe |
| destructive | Delete a recipe. Local server only. |
| write | Push a recipe's ingredients to the grocery list |
Reward Tools
Rewards need Skylight Plus.
Tool | Effect | Description |
| read | Available rewards |
| read | Reward points balance |
| write | Add a reward for one family member (assignee required) |
| write | Change a reward |
| write | Redeem a reward, spending points |
| write | Undo a redemption |
| destructive | Delete a reward. Local server only. |
Photo Tools
Needs Skylight Plus. Local server only.
Tool | Effect | Description |
| read | Photo albums |
Use as a library (experimental)
The package root is a small read-only library for apps such as a family display. It is experimental: breaking changes are allowed in minor versions, and are documented in the changelog, until a real app has used it for a while. The CLI and the MCP servers are unchanged (skylight-mcp runs dist/index.js).
import { createSkylight } from "@davidwoods/skylight-mcp";
const skylight = createSkylight({
frameId: "your_frame_id",
timezone: "America/Los_Angeles",
credential: yourCredentialStore, // or { token: "..." } for a fixed token that does not refresh
});
const events = await skylight.calendar.events({ from: "2026-11-09", to: "2026-11-15" });Read methods only: calendar.events, calendar.sources, profiles.list, chores.list, meals.sittings, meals.recipes, meals.categories, lists.list, lists.items. They return plain objects, never raw Skylight records: no emails, event uids, calendar or account ids, or subscription feed addresses (a feed shows only its host; an email-shaped calendar label is replaced). Event ids are opaque: a repeating event's occurrence ids change after a time edit, so do not store or compare them.
Behaviour an importer should plan for:
chores.listtakes one day ({ date }) or a range ({ from, to }, both included, at most 31 days), plus an optionalprofile. A repeating chore appears once per day it falls on, and each item'sdateis that day. The range is not yet verified against live Skylight.One
createSkylightclient holds one sign-in and caches what it read from your store, including "nothing stored". AftercompleteLoginwrites a new credential, or after anyAuthenticationError, make a new client.Profile names and colours are cached for up to 5 minutes per client, so a renamed profile can show its old name for up to 5 minutes.
If a refreshed credential cannot be written to your store, the call fails with
CredentialSaveError(anAuthenticationError,codeCREDENTIAL_SAVE_FAILED). Refresh tokens rotate, so the old one in your store is already spent: treat it as signed out and sign in again. The error never includes your store's own message.The library writes no per-request log lines. Errors and warnings still go to
console.error.
What is not there, on purpose: writes, deletes, messages, source-calendar and feed management, and any function that takes a URL or a feed address (sign-in lives in the separate setup entry below, which accepts exactly one address: the Skylight redirect you paste). There is no host or fetch option; the API host is fixed in code and an unknown option is refused. Only the package root and /setup are importable (deep imports such as dist/api/client.js fail). The library holds a live sign-in, so it stops accidents and token leaks, not a malicious importer. Verified read-only against a real frame on 2026-11-09 (chores and list items were empty that day).
Signing in from your own app (setup entry, experimental)
An app that keeps its own credential store signs in with the same browser-paste flow as the CLI, through a separate entry. Call it from a setup page only, and protect that page yourself.
import { beginLogin, completeLogin, loginStatus } from "@davidwoods/skylight-mcp/setup";
const { loginUrl } = await beginLogin({ pending }); // show loginUrl; the person signs in at Skylight
await completeLogin({ pending, credential, redirectUrl }); // the https://ourskylight.com/welcome?code=... address they paste
await loginStatus({ credential }); // { signedIn, expired, expiresAt }pendingis your store for the ten-minute pending sign-in:save(value)and an atomictake()(read and delete in one step).credentialis the same store you givecreateSkylight.Nothing secret is returned: the credential is written straight into your store. PKCE (S256) and the state check are always on, and a pending sign-in works once.
The pasted address must be the Skylight redirect (
https://ourskylight.com/welcome); anything else is refused before any request and is never echoed in an error. There is no host or fetch option.Give each app its own sign-in. Refresh tokens rotate, so two apps sharing one credential break each other. A new sign-in did not interrupt two other sessions on the same account: both kept reading in the minutes after it (2026-11-09) and again about 36 hours later, after at least one token refresh each (2026-11-11), with a further sign-in in between. This is one account observed on two days, not a guarantee from Skylight.
Verified against live Skylight under Node on 2026-11-09, and inside the Workers runtime (
nodejs_compat) against a stubbed token endpoint. Not yet run with a Durable Object store.
Skylight API calls used
All calls go to https://app.ourskylight.com/api (pinned in code). {f} is the frame ID. "Live" means run against a real frame by this project; everything else came from upstream and has not been re-run. Messages and meal edit/delete send Skylight-Api-Version: 2026-10-15, the version the web app sends; other calls use the client default.
Function | Calls |
Calendar |
|
Messages |
|
Meals |
|
Recipes |
|
Profiles and family |
|
Chores and routines |
|
Task box |
|
Lists |
|
Rewards |
|
Photos |
|
The planning tools make no calls of their own; they combine the calendar, chore, meal and list calls above.
Example Queries
Once configured, you can ask Claude things like:
"What's on my calendar today?"
"What chores do I need to do this week?"
"Add 'take out trash' to my chores for tomorrow"
"What's on the grocery list?"
"Add milk to my task list"
"Who are the family members on Skylight?"
"How many reward points does each person have?"
Development
# Run in development mode (with hot reload)
npm run dev
# Build
npm run build
# Run tests
npm test
# Type check
npm run typecheckLive tests
npm test uses mocked network calls and is what CI runs. A separate, opt-in
suite runs the real tools against your own Skylight frame to catch drift in
Skylight's API: read smoke checks, response-shape contract checks, and a
create/edit/move/delete round trip on a throwaway meal. It does nothing unless
you set SKYLIGHT_LIVE=1, never sends messages, and cleans up after itself.
Each test is one file in tests/live/, so adding or removing one is adding or
deleting a file. See tests/live/README.md.
SKYLIGHT_FRAME_ID=your_frame_id SKYLIGHT_TIMEZONE=America/Los_Angeles \
SKYLIGHT_LIVE=1 npm run test:liveEach new terminal starts without these variables; if one is missing the suite
says which and prints the full command. To keep them, add the export lines to
~/.zshrc (or ~/.bashrc). On Windows PowerShell use setx and open a new window.
Set SKYLIGHT_TIMEZONE to your frame's zone. It defaults to America/New_York,
which shifts every date window the suite asks for if your frame is elsewhere.
Sign in first with skylight-mcp auth login.
API Documentation
This MCP server is built on top of the reverse-engineered Skylight API. The API endpoints were documented using the skylight-api project, which converts browser network traffic (HAR files) into an OpenAPI specification.
API Resources:
If you discover new API endpoints or find issues with the current documentation, please contribute to the skylight-api repository.
Contributing
Contributions are welcome! Here's how you can help:
Fork the repository and create a feature branch
Make your changes with clear, descriptive commits
Run tests (
npm test) and linting (npm run lint) before submittingOpen a pull request with a description of your changes
Development Setup
git clone https://github.com/dwooods/skylight-mcp.git
cd skylight-mcp
npm install
npm run dev # Start with hot reloadAreas for Contribution
Adding support for new Skylight API endpoints
Improving error handling and edge cases
Enhancing documentation
Writing additional tests
Issues & Support
See SUPPORT.md for what to run first and what must never be pasted into an issue.
Bug reports and feature requests: open an issue and pick a template.
Security problems: use private vulnerability reporting, not a public issue (see SUPPORT.md).
License
MIT
Disclaimer
This is an unofficial integration, not affiliated with or endorsed by Skylight. The Skylight API is reverse-engineered from observed app and web traffic and may change without notice. Sign-in reuses the mobile app's OAuth client. Skylight's Terms of Service prohibit unsupported access and reverse engineering (§5.4) and allow account suspension (§16); you take that risk for the account you use. This project only exposes features your Skylight subscription includes. Use at your own risk.
Available Tools
30 toolscreate_calendar_eventA
Create a new calendar event in Skylight.
Use this when:
Scheduling a new event: "Add a dentist appointment on Friday at 2pm"
Creating family activities: "Schedule soccer practice every Saturday at 10am"
Adding reminders: "Put Mom's birthday on the calendar"
Parameters:
summary (required): Event title (e.g., "Dentist Appointment")
startsAt (required): Start time in ISO format or natural language
endsAt (required): End time in ISO format or natural language
allDay: Set to true for all-day events
description: Additional notes for the event
location: Where the event takes place
categoryIds: Family member IDs to associate with the event
Returns: The created event details.
Related: Use get_family_members to get category IDs for assignments.
Inputs: endsAt (required), summary (required), startsAt (required), allDay, +3 more.
create_choreB
Add a new chore to Skylight.
Use this when the user wants to:
Add a new task like "empty the dishwasher"
Assign chores to family members
Create recurring chores
The chore will appear on the Skylight display.
Inputs: summary (required), date, time, assignee, +3 more.
create_listA
Create a new list in Skylight.
Use this when:
Creating a new shopping/grocery list
Creating a new to-do list
Parameters:
label (required): Name of the list (e.g., "Vacation Packing", "Weekly Groceries")
kind (required): "shopping" for grocery/shopping lists, "to_do" for task lists
color: Optional color for the list
Returns: The created list details.
Inputs: kind (required), label (required), color.
create_list_itemA
Add an item to a Skylight list.
Use this when:
Adding something to the grocery list: "Add milk to the shopping list"
Creating a to-do item: "Put 'call doctor' on my to-do list"
Adding items to any list
Parameters:
label (required): The item text (e.g., "Milk", "Call doctor")
listId: ID of the list to add to
listName: Name of the list to add to (e.g., "Grocery List")
section: Category within the list (e.g., "Dairy", "Produce")
If no list is specified, adds to the default grocery list.
Returns: Confirmation of the added item.
Related: Use get_lists to see available lists and their IDs.
Inputs: label (required), listId, section, listName.
create_taskA
Add a task to the Skylight task box.
The task box holds unscheduled tasks that can later be assigned to specific dates.
Use this when the user says:
"Add XYZ to my task list"
"Remind me to do ABC" (without a specific date)
"Put 'clean garage' on the task box"
The task will appear on the Skylight display in the task box.
Inputs: summary (required), emoji, routine, rewardPoints.
delete_calendar_eventADestructiveIdempotent
Delete a calendar event from Skylight.
Use this when:
Canceling an event: "Remove the dentist appointment"
Deleting old events: "Delete the meeting from yesterday"
Parameters:
eventId (required): ID of the event to delete (from get_calendar_events)
Note: This permanently removes the event. For recurring events, this may only delete one instance.
Inputs: eventId (required).
delete_choreADestructiveIdempotent
Delete a chore from Skylight.
Use this when:
Removing an old or irrelevant chore
Deleting a chore that was added by mistake
Parameters:
choreId (required): ID of the chore to delete (from get_chores)
Note: This permanently removes the chore. For recurring chores, this may only delete one instance.
Inputs: choreId (required).
delete_listADestructiveIdempotent
Delete a list from Skylight.
Use this when:
Removing an old or unused list
Deleting a temporary list
Parameters:
listId: ID of the list to delete (use get_lists to find IDs)
listName: Name of the list to delete (alternative to listId)
Note: This permanently deletes the list and all its items.
Inputs: listId, listName.
delete_list_itemADestructiveIdempotent
Remove an item from a list.
Use this when:
Removing an item that was added by mistake
Deleting an item instead of marking it complete
Parameters:
itemId (required): ID of the item to delete
listId (required): ID of the list containing the item
Note: This permanently removes the item. Use update_list_item with status="completed" to check it off instead.
Inputs: itemId (required), listId (required).
find_conflictsARead-onlyIdempotent
Find calendar events that overlap in time within a date range. All-day events never count as conflicts.
Returns each overlapping pair with the overlap window, or "none".
Inputs: date, days, dateEnd.
find_free_slotsARead-onlyIdempotent
Find free time of at least a given length on each day, between an earliest and latest time, around the calendar's timed events. All-day events are not treated as busy.
Use for "when could we do X this week?" questions.
Inputs: date, days, latest, dateEnd, +2 more.
get_avatarsARead-onlyIdempotent
Get available avatar options for Skylight profiles.
Use this when:
Setting up a new family member profile
Changing someone's profile picture
Exploring available avatar options
Returns: List of available avatars with their IDs and details.
Inputs: no named parameters.
get_calendar_eventsARead-onlyIdempotent
Get calendar events from Skylight.
Use this to answer questions like:
"What's on my calendar today?"
"What do we have scheduled this weekend?"
"Are there any events on Friday?"
Returns a list of events with their titles, times, and details.
Inputs: date, dateEnd.
get_choresARead-onlyIdempotent
Get chores from Skylight.
Use this to answer:
"What chores do I need to do today?"
"Show me this week's chores"
"What's on the chore chart?"
"What chores does [name] have?"
Returns chores with their assignees, due dates, and completion status.
Inputs: date, status, dateEnd, assignee, +1 more.
get_colorsARead-onlyIdempotent
Get available color options for Skylight profiles and lists.
Use this when:
Choosing a color for a family member profile
Setting a list color
Exploring available color options
Returns: List of available colors with their IDs and hex values.
Inputs: no named parameters.
get_devicesARead-onlyIdempotent
List Skylight devices in the household.
Use this to answer:
"What Skylight devices do we have?"
"How many Skylight frames are connected?"
Inputs: no named parameters.
get_family_membersARead-onlyIdempotent
Get family members/profiles from Skylight.
Shows who can be assigned chores and their profile details.
Use this to answer:
"Who's in our family on Skylight?"
"What family members are set up?"
"Who can I assign chores to?"
Inputs: no named parameters.
get_frame_infoARead-onlyIdempotent
Get Skylight household/frame information.
Useful for setup verification and debugging.
Use this to answer:
"Show Skylight household info"
"What's my frame ID?"
Inputs: no named parameters.
get_list_itemsARead-onlyIdempotent
Get items from a specific Skylight list.
Use this to answer:
"What's on the grocery list?"
"Show me my to-do list"
"What items are on [list name]?"
Returns items organized by section with their completion status.
Inputs: listName, listType, includeCompleted.
get_listsARead-onlyIdempotent
Get all lists from Skylight (grocery lists, to-do lists, etc.).
Use this to see what lists are available before adding items. Returns list names, types (shopping/to_do), and item counts.
Inputs: no named parameters.
get_messagesARead-onlyIdempotent
Get messages (Skylight calls them "nudges") shown on the family frame.
Use this when:
Checking what has already been posted to the frame
Avoiding a duplicate before sending a reminder
Returns: Messages grouped by day with time, text, recipients and sender. Defaults to the last 7 days through the next 7 days. Max 31 days.
Inputs: date, dateEnd.
get_source_calendarsARead-onlyIdempotent
Get connected calendar sources synced to Skylight.
Use this to answer:
"Which calendars are synced to Skylight?"
"What calendar accounts are connected?"
Returns a list of connected calendar sources (Google, iCloud, etc.).
Inputs: no named parameters.
get_week_overviewARead-onlyIdempotent
One compact view of the family's week: calendar events, chores and tasks (with status and assignee), planned meals, and list summaries, day by day, plus any scheduling conflicts.
Use this first when planning anything (dinners, errands, who drives). It replaces separate calls to get_calendar_events, get_chores, get_meal_sittings and get_lists for the same range.
Parameters:
date: first day (YYYY-MM-DD, 'today', 'monday'...). Default today.
days: how many days (1–31). Default 7. Or give dateEnd instead.
includeLists: include grocery/to-do list summaries. Default true.
Inputs: date, days, dateEnd, includeLists.
plan_week_mealsA
Schedule several meals in one call - Plus subscription required.
Each entry names a date, a meal (category name like "Dinner", default Dinner), and either a free-text summary ("Thai green curry") or a saved recipeId. A slot that already has a meal is skipped and reported, never overwritten. Use dryRun first to see what would change.
Parameters:
meals: list of { date, meal?, summary?, recipeId? }
dryRun: report the plan without writing. Default false.
addRecipesToGroceryList: for entries with a recipeId, also push that recipe's ingredients to the grocery list. Default false.
Inputs: meals (required), dryRun, addRecipesToGroceryList.
send_messageA
Post a message to the family frame (shown to everyone, on all devices, now).
PERMANENT: Skylight has no way to delete a message, and it appears on the family frame for the whole household immediately.
Two-step by design. Called without confirm it only previews and posts nothing. Show the user the exact text and get a clear yes, then call again with confirm: true. Never pass confirm: true on the first call, and never invent message text the user did not ask for. Read aloud is OFF by default; enable it only if the user asked.
Check get_messages first to avoid posting a duplicate.
Inputs: body (required), from, confirm, readAloud.
update_calendar_eventA
Update an existing calendar event.
Use this when:
Changing event time: "Move the dentist appointment to 3pm"
Updating event details: "Add location to the meeting"
Renaming an event: "Change 'Doctor' to 'Dr. Smith checkup'"
Parameters:
eventId (required): ID of the event to update (from get_calendar_events)
summary: New title for the event
startsAt: New start time (ISO format)
endsAt: New end time (ISO format)
description: Updated notes
location: Updated location
categoryIds: Updated family member assignments
Returns: The updated event details.
Inputs: eventId (required), allDay, endsAt, summary, +4 more.
update_choreA
Update an existing chore in Skylight.
Use this when:
Marking a chore as complete: "Mark 'dishes' as done"
Changing chore assignment: "Reassign the trash to Dad"
Updating chore details: "Change the time for the homework chore"
Parameters:
choreId (required): ID of the chore (from get_chores)
summary: New description for the chore
status: "completed" to mark done, "pending" to mark incomplete
date: New due date
time: New due time
assignee: New family member assignment
Returns: The updated chore details.
Inputs: choreId (required), date, time, status, +3 more.
update_listA
Update an existing list's name or settings.
Use this when:
Renaming a list
Changing a list's type or color
Parameters:
listId: ID of the list to update (use get_lists to find IDs)
listName: Name of the list to update (alternative to listId)
label: New name for the list
kind: New type ("shopping" or "to_do")
color: New color for the list
Returns: The updated list details.
Inputs: kind, color, label, listId, +1 more.
update_list_itemA
Update a list item (mark complete, rename, move to section).
Use this when:
Marking an item as complete: "Check off milk from the list"
Renaming an item: "Change 'milk' to '2% milk'"
Moving an item to a different section
Parameters:
itemId (required): ID of the item to update
listId (required): ID of the list containing the item
label: New text for the item
status: "completed" to check off, "pending" to uncheck
section: Move to a different section
Returns: The updated item details.
Inputs: itemId (required), listId (required), label, status, +1 more.
update_messageA
Edit the text (and optionally the sender) of a message already on the frame.
Only messages sent to everyone on all devices and not repeating can be edited; anything else must be edited in the Skylight app. Delivery time and read-aloud setting are kept. The edit is visible on the frame immediately. Get the message ID from get_messages (looks within 31 days either side of today).
Inputs: body (required), messageId (required), from.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 30 tools
Most tools target clearly distinct resources and actions, and descriptions explicitly clarify boundaries like create_task vs create_chore vs create_list_item. The main overlap is get_week_overview duplicating individual getters for calendar, chores, and lists, plus possible confusion between dedicated conflict/free-slot tools and overview features.
Names consistently use snake_case and a predictable verb_noun pattern: get_*, create_*, update_*, delete_*, plus send_message and plan_week_meals. The special verbs still fit the resource-oriented convention and do not introduce inconsistent casing or styles.
The server covers several distinct domains (calendar, chores, lists, messages, meals, profiles/devices), so many CRUD tools are justified. However, 30 tools is heavy for an MCP surface, and some metadata getters or aggregate/individual overlaps could likely be consolidated.
Calendar events, chores, lists, and list items have full CRUD coverage, and messages are well covered given the permanent-post limitation. Notable gaps remain around tasks (create only, with no standalone update/delete) and meals (planning exists, but retrieval/update/delete lifecycle is thin).
Maintenance
Related MCP Connectors
The first official family-planner MCP: tasks, calendar, meals, shopping and reminders.
Private family planner for schedules, tasks, checklists, attachments, and availability.
Family board: calendar, chores, shopping, meals, pets; OAuth for one household.
Authenticated MCP for a user's care circle: read CareEvents, list loved ones, inspect workspace.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceUnofficial MCP server for Skylight Calendar that enables calendar, task, list, meal, and profile management through typed commands and SDK integration.73 npm1MIT
- AlicenseCqualityAmaintenanceMCP server for Skylight Calendar family hub, providing over 100 tools to manage events, lists, chores, meals, messages, albums, and device settings.1161,487 npm23MIT
- AlicenseNot gradedqualityCmaintenanceEnables mixed-schedule families to coordinate quiet hours, meals, chores, calendar events, nudges, and wake briefings through an MCP client such as a living-room Alexa+ Show simulator.3 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables households to maintain a shared, persistent family memory for tracking item locations, warranty dates, chores, and who said what and when. It exposes this memory to AI assistants and tooling through MCP-compatible endpoints.8,811 npmMIT