@davidwoods/skylight-mcp 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +633 -584
  3. package/THIRD_PARTY_NOTICES.md +38 -38
  4. package/dist/api/credential-store.d.ts.map +1 -1
  5. package/dist/api/credential-store.js +2 -1
  6. package/dist/api/credential-store.js.map +1 -1
  7. package/dist/api/endpoints/categories.d.ts +6 -6
  8. package/dist/api/endpoints/categories.d.ts.map +1 -1
  9. package/dist/api/endpoints/categories.js +6 -13
  10. package/dist/api/endpoints/categories.js.map +1 -1
  11. package/dist/api/endpoints/chores.d.ts +8 -0
  12. package/dist/api/endpoints/chores.d.ts.map +1 -1
  13. package/dist/api/endpoints/chores.js +9 -0
  14. package/dist/api/endpoints/chores.js.map +1 -1
  15. package/dist/api/endpoints/taskbox.d.ts +8 -0
  16. package/dist/api/endpoints/taskbox.d.ts.map +1 -1
  17. package/dist/api/endpoints/taskbox.js +17 -0
  18. package/dist/api/endpoints/taskbox.js.map +1 -1
  19. package/dist/api/types.d.ts +14 -0
  20. package/dist/api/types.d.ts.map +1 -1
  21. package/dist/cli.d.ts.map +1 -1
  22. package/dist/cli.js +9 -8
  23. package/dist/cli.js.map +1 -1
  24. package/dist/config.js +23 -23
  25. package/dist/doctor.d.ts +10 -0
  26. package/dist/doctor.d.ts.map +1 -1
  27. package/dist/doctor.js +23 -7
  28. package/dist/doctor.js.map +1 -1
  29. package/dist/tools/calendar.d.ts +5 -0
  30. package/dist/tools/calendar.d.ts.map +1 -1
  31. package/dist/tools/calendar.js +107 -71
  32. package/dist/tools/calendar.js.map +1 -1
  33. package/dist/tools/chores.d.ts.map +1 -1
  34. package/dist/tools/chores.js +53 -81
  35. package/dist/tools/chores.js.map +1 -1
  36. package/dist/tools/family.js +17 -17
  37. package/dist/tools/lists.js +85 -85
  38. package/dist/tools/meals.js +102 -102
  39. package/dist/tools/messages.d.ts.map +1 -1
  40. package/dist/tools/messages.js +37 -49
  41. package/dist/tools/messages.js.map +1 -1
  42. package/dist/tools/misc.js +14 -14
  43. package/dist/tools/photos.js +6 -6
  44. package/dist/tools/planning.d.ts.map +1 -1
  45. package/dist/tools/planning.js +24 -23
  46. package/dist/tools/planning.js.map +1 -1
  47. package/dist/tools/registry.d.ts.map +1 -1
  48. package/dist/tools/registry.js +1 -0
  49. package/dist/tools/registry.js.map +1 -1
  50. package/dist/tools/rewards.d.ts.map +1 -1
  51. package/dist/tools/rewards.js +74 -90
  52. package/dist/tools/rewards.js.map +1 -1
  53. package/dist/tools/tasks.d.ts.map +1 -1
  54. package/dist/tools/tasks.js +45 -10
  55. package/dist/tools/tasks.js.map +1 -1
  56. package/dist/utils/errors.js +16 -16
  57. package/package.json +78 -78
package/README.md CHANGED
@@ -1,584 +1,633 @@
1
- # Skylight MCP Server
2
-
3
- An MCP (Model Context Protocol) server for the Skylight Calendar. Lets Claude
4
- (including Claude Duet, via the hosted mode) read and plan against your family
5
- calendar, chores, lists, meals and rewards.
6
-
7
- > **Unofficial.** This project is not affiliated with or endorsed by Skylight.
8
- > Skylight has no public API; this talks to the same private endpoints their
9
- > own apps use, discovered by observing app traffic. It can stop working or be
10
- > blocked at any time, without notice. Use it with your own account, at your
11
- > own risk, and keep request volume modest.
12
- >
13
- > Forked from [TheEagleByte/skylight-mcp](https://github.com/TheEagleByte/skylight-mcp)
14
- > (MIT). The sign-in and credential-store design follows
15
- > [kamilio/skylight-calendar-agent](https://github.com/kamilio/skylight-calendar-agent)
16
- > (MIT). See `THIRD_PARTY_NOTICES.md`.
17
-
18
- ## Features
19
-
20
- - **Calendar**: read events and connected calendars; create, update and delete events
21
- - **Planning**: one-call week overview, overlap detection, free-slot search, bulk meal planning
22
- - **Meals** (Plus): see, add, rename, move and delete scheduled meals; browse and edit recipes
23
- - **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_message` but was not tested live).
24
- - **Chores and tasks**: read, create, update and delete chores; add to the task box
25
- - **Lists**: read, create, update and delete lists and items
26
- - **Family**: read profiles, devices and household info
27
- - **Rewards** (Plus): read points and rewards; create, update, redeem and delete
28
-
29
- See [What is supported](#what-is-supported) for what is tested, what is not, and
30
- what the hosted server leaves out.
31
-
32
- ## Quick Start
33
-
34
- You need Node 20+, a Skylight account, and an MCP client (Claude desktop, Claude
35
- Code, or any other). Nothing to clone or build.
36
-
37
- ### 1. Sign in (once)
38
-
39
- ```bash
40
- npx -y @davidwoods/skylight-mcp auth login
41
- ```
42
-
43
- This prints a Skylight sign-in URL. Open it in your browser and sign in. The
44
- browser bounces through `https://ourskylight.com/welcome?code=...` and on to
45
- the Skylight home page too fast to copy. Get it from your browser history:
46
- `Cmd-Y` (Mac) / `Ctrl-H`, search `welcome?code`, right-click the newest
47
- Skylight result, Copy link address. Then:
48
-
49
- ```bash
50
- npx -y @davidwoods/skylight-mcp auth complete
51
- ```
52
-
53
- and paste it. Your password never touches this program; only the resulting
54
- OAuth tokens are stored, encrypted with a key in the macOS Keychain (plain
55
- `0600` file on Linux/Windows). macOS may ask once to allow Keychain access.
56
- `auth status` and `auth logout` do what they say.
57
-
58
- ### 2. Find your frame ID and timezone
59
-
60
- Your frame ID is the number in `https://ourskylight.com/my-skylight/<number>`
61
- after you sign in to the Skylight web app (see
62
- [Finding your Frame ID](#finding-your-frame-id)). Your timezone is an IANA name
63
- such as `America/Chicago`. **Set it**: it defaults to `America/New_York`, and
64
- event times and date windows come out wrong anywhere else.
65
-
66
- ### 3. Add to your MCP client
67
-
68
- **Claude desktop** (Settings → Developer → Edit Config), or any client that uses `mcp.json`:
69
- ```json
70
- {
71
- "mcpServers": {
72
- "skylight": {
73
- "command": "npx",
74
- "args": ["-y", "@davidwoods/skylight-mcp"],
75
- "env": {
76
- "SKYLIGHT_FRAME_ID": "your_frame_id",
77
- "SKYLIGHT_TIMEZONE": "America/New_York"
78
- }
79
- }
80
- }
81
- }
82
- ```
83
-
84
- **Claude Code:**
85
- ```bash
86
- claude mcp add skylight \
87
- -e SKYLIGHT_FRAME_ID=your_frame_id -e SKYLIGHT_TIMEZONE=America/New_York \
88
- -- npx -y @davidwoods/skylight-mcp
89
- ```
90
-
91
- Restart the client fully (Cmd-Q on Mac), then ask it "what's on the family
92
- calendar this week?"
93
-
94
- ### 4. Check it
95
-
96
- ```bash
97
- SKYLIGHT_FRAME_ID=your_frame_id SKYLIGHT_TIMEZONE=America/New_York \
98
- npx -y @davidwoods/skylight-mcp doctor
99
- ```
100
-
101
- `doctor` checks Node, the frame ID, the timezone, your sign-in and one real call
102
- to Skylight, and says what to fix. It never prints tokens or your frame ID.
103
- Add `--offline` to skip the network call.
104
-
105
- ### From source (contributors)
106
-
107
- ```bash
108
- git clone https://github.com/dwooods/skylight-mcp.git
109
- cd skylight-mcp && npm install && npm run build
110
- node dist/index.js auth login # same steps as above, using node dist/index.js
111
- ```
112
-
113
- In the client config use `"command": "node", "args": ["/path/to/skylight-mcp/dist/index.js"]`.
114
-
115
- ### Windows (not yet verified)
116
-
117
- Windows is untested as of this writing; treat these as expected, not confirmed.
118
-
119
- - `npx` is a `.cmd` file on Windows, so in a client config use
120
- `"command": "cmd", "args": ["/c", "npx", "-y", "@davidwoods/skylight-mcp"]`.
121
- - Set variables in PowerShell like this, then run the command on the next line:
122
- `$env:SKYLIGHT_FRAME_ID="your_frame_id"; $env:SKYLIGHT_TIMEZONE="America/New_York"`.
123
- - The sign-in is stored unencrypted in `%APPDATA%\skylight-mcp\credential.json`.
124
- There is no Keychain equivalent wired in, and Windows ignores Unix `0600`
125
- permissions. Only your Windows user profile protects it.
126
-
127
- ### Troubleshooting
128
-
129
- | Symptom | Likely cause and fix |
130
- |---|---|
131
- | Server fails to start, mentions configuration | `SKYLIGHT_FRAME_ID` missing. Run `doctor`. |
132
- | "Not signed in" | Run `auth login` then `auth complete`. Logins expire after 10 minutes. |
133
- | macOS Keychain error | The server refuses to fall back to plaintext. Unlock the Keychain or allow access when prompted. |
134
- | Events show at the wrong hour | `SKYLIGHT_TIMEZONE` unset (defaults to New York). Set your zone. |
135
- | 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". |
136
- | Worked yesterday, fails today | Skylight has no public API and may have changed. Run `doctor`, then check the issues. |
137
-
138
- ### Instructions for AI
139
-
140
- Copy this into your AI's custom instructions or system prompt:
141
-
142
- > 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.
143
- >
144
- > Tips:
145
- > - Call `get_family_members` before assigning chores to get member names
146
- > - Grocery items default to the main grocery list if no list specified
147
- > - Dates accept "today", "tomorrow", day names, or YYYY-MM-DD format
148
- > - Some tools (rewards, meals, photos) require Skylight Plus subscription
149
-
150
- ## Prerequisites
151
-
152
- - Node.js 20+
153
- - A Skylight account with an active subscription
154
- - Your Skylight Frame ID (see [Finding your Frame ID](#finding-your-frame-id))
155
-
156
- ## Authentication
157
-
158
- Sources are tried in this order:
159
-
160
- 1. **`SKYLIGHT_TOKEN`** - an explicit token in the environment, if set.
161
- 2. **Stored OAuth credential** (recommended) - created by `skylight-mcp auth login`
162
- / `auth complete`. The access token is refreshed automatically; refresh tokens
163
- rotate and the rotated token is persisted.
164
- 3. **`SKYLIGHT_EMAIL` + `SKYLIGHT_PASSWORD`** (deprecated) - the server replays
165
- Skylight's web login form itself. This works but puts your password in your
166
- MCP client config in plaintext. Prefer the browser sign-in above.
167
-
168
- If none is present the server fails with instructions rather than starting.
169
-
170
- Where the credential lives:
171
-
172
- | Platform | Location | Protection |
173
- |----------|----------|------------|
174
- | macOS | `~/Library/Application Support/skylight-mcp/credential.enc.json` | AES-256-GCM; key in Keychain (`skylight-mcp.encryption-key`) |
175
- | Linux / other | `$XDG_CONFIG_HOME/skylight-mcp/credential.json` (default `~/.config/skylight-mcp/`) | `0600` file in a `0700` directory, no encryption |
176
- | Windows | `%APPDATA%\\skylight-mcp\\credential.json` | Not encrypted. Windows ignores `0600`; only your user profile's own permissions protect it. Untested, see below. |
177
-
178
- If the Keychain is unavailable on macOS the server refuses to read or write the
179
- credential; it never silently downgrades to plaintext.
180
-
181
- ### Finding your Frame ID
182
-
183
- The frame ID is the household identifier. The easy way: after signing in, the
184
- Skylight web app lands on `https://ourskylight.com/my-skylight/<number>`. That
185
- number is your frame ID. Treat it as a secret.
186
-
187
- The hard way, if that URL doesn't appear:
188
-
189
- 1. Use a proxy tool ([Proxyman](https://proxyman.io/), [Charles](https://www.charlesproxy.com/), or [mitmproxy](https://mitmproxy.org/))
190
- 2. Capture any API request from the Skylight app
191
- 3. Look at the URL path: `/api/frames/{frameId}/...`
192
- 4. Example: `/api/frames/abc123/chores` → frame ID is `abc123`
193
-
194
- ## Configuration
195
-
196
- | Variable | Required | Description |
197
- |----------|----------|-------------|
198
- | `SKYLIGHT_TOKEN` | No | Explicit API token; overrides the stored credential |
199
- | `SKYLIGHT_AUTH_TYPE` | No | `bearer` (default) or `basic` (only with `SKYLIGHT_TOKEN`) |
200
- | `SKYLIGHT_EMAIL` | No (deprecated) | Fallback email/password login |
201
- | `SKYLIGHT_PASSWORD` | No (deprecated) | Fallback email/password login |
202
- | `SKYLIGHT_FRAME_ID` | Yes | Your household frame ID |
203
- | `SKYLIGHT_TIMEZONE` | No | Default timezone (default: `America/New_York`) |
204
-
205
- ### Auth Notes
206
-
207
- - The API host is pinned to `https://app.ourskylight.com`. No environment
208
- variable or `.env` file can redirect requests (and the bearer token) elsewhere.
209
- - `auth logout` deletes the local credential only; Skylight has no public way to
210
- revoke a session, so change your password if a credential may have leaked.
211
- - Skylight has no official API. This uses the same OAuth client the Skylight
212
- app uses, and it may break or be blocked at any time.
213
-
214
- ## Hosted server (Claude Duet, Claude on other devices)
215
-
216
- The same code also runs as a remote MCP server on Cloudflare Workers, which is
217
- how Claude Duet (and any Claude client that can't reach your Mac) connects.
218
- One Worker is both the OAuth authorization server Claude talks to and the MCP
219
- resource server. Signing in is the same browser-paste flow as the CLI; your
220
- Skylight password never reaches the Worker, and the Skylight tokens are kept
221
- encrypted in the OAuth grant, not in a database you manage.
222
-
223
- ### Which tools the hosted server exposes, and why
224
-
225
- By default the hosted server registers **read and create/update tools only**:
226
- no `delete_*` tools and no photo uploads. The local (stdio) server registers
227
- everything. This is a deliberate choice, not a limitation of the Worker:
228
-
229
- - The hosted server is what Claude Duet drives proactively, sometimes
230
- unattended. A misread instruction that *creates* a wrong chore is a
231
- nuisance; one that *deletes* a calendar event is data loss with no undo
232
- from Claude. Leaving deletes off the hosted path caps the worst outcome at
233
- "something extra to clean up".
234
- - Photo tools are uploads the Worker has no reason to carry.
235
-
236
- If you want full parity on the hosted server, change one line in
237
- `src/worker/mcp.ts` and redeploy:
238
-
239
- ```ts
240
- export const HOSTED_EFFECTS: readonly ToolEffect[] = ["read", "write", "destructive"];
241
- ```
242
-
243
- and set `photos: true` in the same file if you also want photo uploads.
244
- Every tool's classification lives in `src/tools/registry.ts`, so you can also
245
- carve out a profile of your own (read-only, for example, is `["read"]`).
246
-
247
- If you run both servers in the Claude desktop app, Claude sees two sets of
248
- mostly duplicate tools and may use either for writes. Disable the local one
249
- in Settings → Connectors when you do not need the delete or photo tools, or
250
- say "use the local Skylight server" when you do.
251
-
252
- ### Deploy (once)
253
-
254
- You need a free Cloudflare account. From a clone of this repo:
255
-
256
- ```bash
257
- npm install
258
- npx wrangler login
259
- npm run worker:setup # add -- --dry-run first to see the steps
260
- ```
261
-
262
- `worker:setup` creates your KV namespace, writes a gitignored
263
- `wrangler.local.jsonc` with your real ID (the committed `wrangler.jsonc` keeps a
264
- placeholder, so nothing account-specific can be committed), asks you for a
265
- sign-in passphrase, deploys, sets `WORKER_URL`, deploys again and prints the
266
- connector URL. Re-deploy later with `npm run worker:deploy:local`.
267
-
268
- Doing it by hand instead:
269
-
270
- ```bash
271
- cp wrangler.jsonc wrangler.local.jsonc # gitignored: your real IDs live only here
272
- npx wrangler kv namespace create OAUTH_KV # paste the id into wrangler.local.jsonc, not wrangler.jsonc
273
- npx wrangler secret put SETUP_PASSPHRASE -c wrangler.local.jsonc # 12+ chars; required to link an account
274
- npm run worker:deploy:local # prints the Worker's URL
275
- npx wrangler secret put WORKER_URL -c wrangler.local.jsonc # paste that URL (origin only, no path)
276
- npm run worker:deploy:local
277
- ```
278
-
279
- `WORKER_URL` must be exactly the public origin of the Worker; every token is
280
- bound to it. It is a secret rather than a `vars` entry so the server's
281
- address is not published with the source.
282
-
283
- ### Connect from Claude
284
-
285
- In Claude (or Duet) → Settings → Connectors → Add custom connector:
286
-
287
- - Name: `Skylight`
288
- - URL: `https://skylight-mcp.<your-subdomain>.workers.dev/mcp`
289
-
290
- Claude opens the consent page. Enter the setup passphrase, your frame ID and
291
- timezone, then follow the two-step Skylight sign-in and paste the redirect
292
- URL. Connectors are shared across your Claude account, so Duet sees it too.
293
-
294
- ### What is stored where
295
-
296
- | Thing | Where | Protection |
297
- |---|---|---|
298
- | Skylight access + refresh tokens | OAuth grant props in `OAUTH_KV` | Encrypted by workers-oauth-provider; the key is derived from the token Claude holds, so KV alone is not enough |
299
- | PKCE verifier during sign-in | Upstream transaction in `OAUTH_KV` | Bound to your browser by a `__Host-` cookie; single use; 10-minute TTL |
300
- | Setup passphrase | Worker secret | Never in the repo or `wrangler.jsonc` |
301
- | Nothing else | | The Worker has no database and no logs of tokens |
302
-
303
- To revoke: remove the connector in Claude (revokes the grant), or
304
- `npx wrangler kv key list --binding OAUTH_KV` and delete, or change your
305
- Skylight password.
306
-
307
- ## What is supported
308
-
309
- Status vocabulary, used honestly:
310
-
311
- - **Live-tested**: run against a real Skylight frame by this project, with the result checked by reading back.
312
- - **Inherited**: came from the upstream project and works there, but this project has not re-run it live.
313
- - **Mocked only**: covered by this project's mocked tests; not yet run against real data.
314
-
315
- Skylight has no public API. Everything below can break when Skylight ships a
316
- web release, which happens without notice. The web app version and API version
317
- last captured are recorded in the repository's build log.
318
-
319
- | Area | What works | Hosted server (Claude Duet) | Status |
320
- |------|------------|------------------------------|--------|
321
- | Calendar | Read events and source calendars; create (with profiles), update, delete events | Everything except delete | Read, create and delete live-tested; update inherited |
322
- | Planning | Week overview, conflicts, free slots, bulk meal plan | All four | Week overview live-tested; the rest mocked only |
323
- | Meals (Plus) | Read categories and meals; create, rename, move, delete one-off meals | Everything except delete | Live-tested |
324
- | Recipes (Plus) | Read, create, update, delete recipes; push ingredients to the grocery list | Everything except delete | Inherited |
325
- | 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 |
326
- | Chores | Read, create, update, delete | Everything except delete | Read live-tested; writes inherited |
327
- | Task box | Add a task | Yes | Inherited |
328
- | Lists | Read lists and items; create, update, delete lists and items | Everything except delete | Inherited |
329
- | Family and devices | Profiles with IDs, avatars, colours, household info, devices | Yes | Profiles live-tested; the rest inherited |
330
- | Rewards (Plus) | Read points and rewards; create, update, redeem, unredeem, delete | Everything except delete | Inherited |
331
- | Photos (Plus) | List albums | No | Inherited |
332
-
333
- ### Not supported
334
-
335
- Nothing here is hidden behind a flag. It is either unavailable from Skylight or not yet captured.
336
-
337
- - **Messages**: deleting one (Skylight offers no way), sending to specific profiles or devices, scheduling, repeating, voice notes, and read-aloud (the option exists on `send_message` but was not tested live).
338
- - **Meals**: editing or deleting a repeating meal (it is refused, because the behaviour of changing one occurrence of a series was not captured), saving a meal to the recipe box from the edit form, and Skylight's own "Plan Meals" feature.
339
- - **Task box**: listing, editing or completing tasks. It is create-only.
340
- - **Profiles**: creating or editing them. Read-only.
341
- - **Settings, account, notifications, sleep schedule, device navigation**: none of these are exposed.
342
- - **Photos**: uploads and anything beyond listing albums.
343
- - **Accounts with more than one frame**: the server is pinned to one frame ID.
344
- - **Credentials**: sign-in, sign-out and token access are CLI commands (`skylight-mcp auth ...`) and are deliberately not MCP tools.
345
-
346
- ### Keeping this honest
347
-
348
- Every tool must appear in the tables below with the effect it really has, and a
349
- test fails if one is missing or mislabelled. When you add, change or remove a
350
- tool, update this section in the same change.
351
-
352
- ## Available Tools
353
-
354
- Effects: **read** changes nothing; **write** creates or edits and can be edited
355
- again; **destructive** cannot be undone from the API. The hosted server registers
356
- read and write only, so destructive tools exist on the local server alone.
357
-
358
- ### Planning Tools
359
-
360
- Built for "what does the week look like and what is for dinner" questions.
361
- One call returns calendar, chores/tasks, meals and lists, merged per day.
362
-
363
- | Tool | Effect | Description |
364
- |------|--------|-------------|
365
- | `get_week_overview` | 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. |
366
- | `find_conflicts` | read | Timed events that overlap in a date range. All-day events never conflict. |
367
- | `find_free_slots` | read | Gaps of at least N minutes inside a daily window (default 08:00 to 21:00). |
368
- | `plan_week_meals` | write | Fill several meal slots at once. `dryRun` previews; occupied slots are skipped, never overwritten; all entries are validated before anything is written; optional grocery push for recipes. |
369
-
370
- Times are computed in the frame's time zone, including DST changes.
371
-
372
- ### Message Tools
373
-
374
- Messages are the notes shown on the frame (Skylight's web app calls them "nudges").
375
-
376
- | Tool | Effect | Description |
377
- |------|--------|-------------|
378
- | `get_messages` | read | Messages in a date range with time, text, recipients and sender. Never returns the presigned audio URLs. |
379
- | `send_message` | write | Post a message to the frame now, on all devices. Previews by default and posts only with `confirm: true`. Read aloud is **off** unless asked for. `to` names the profiles to send to (see `get_family_members`); omit it for everyone. Optional sender profile. Up to 300 characters. The app has no delete button, but `delete_message` removes one. |
380
- | `update_message` | 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. |
381
- | `delete_message` | destructive | Delete a message. The app has no delete button; this uses `DELETE /nudges/{id}`, documented by other Skylight clients and verified on a real frame. Available on both the local and hosted servers (the one destructive tool the hosted server offers). Previews unless `confirm: true`, then reads back and reports success only if the message is gone. |
382
-
383
- ### Calendar Tools
384
-
385
- | Tool | Effect | Description |
386
- |------|--------|-------------|
387
- | `get_calendar_events` | read | Events for a date range |
388
- | `get_source_calendars` | read | Connected calendar sources (Google, iCloud, etc.) |
389
- | `create_calendar_event` | write | Add an event. `profiles` assigns it to named family members. Local times are sent with the frame's UTC offset. |
390
- | `update_calendar_event` | write | Change an event, including who it is assigned to (`profiles`) |
391
- | `delete_calendar_event` | destructive | Delete an event. Local server only. |
392
-
393
- ### Chore Tools
394
-
395
- | Tool | Effect | Description |
396
- |------|--------|-------------|
397
- | `get_chores` | read | Chores with optional filters (date, assignee, status) |
398
- | `create_chore` | write | Create a chore, optionally recurring |
399
- | `update_chore` | write | Rename, reschedule, reassign, complete or reopen a chore |
400
- | `delete_chore` | destructive | Delete a chore. Local server only. |
401
-
402
- ### Task Tools
403
-
404
- | Tool | Effect | Description |
405
- |------|--------|-------------|
406
- | `create_task` | write | Add a task to the task box (create-only; there is no list tool) |
407
-
408
- ### List Tools
409
-
410
- | Tool | Effect | Description |
411
- |------|--------|-------------|
412
- | `get_lists` | read | All lists |
413
- | `get_list_items` | read | Items in one list |
414
- | `create_list` | write | Create a list |
415
- | `update_list` | write | Rename or change a list |
416
- | `create_list_item` | write | Add an item |
417
- | `update_list_item` | write | Edit or tick off an item |
418
- | `delete_list` | destructive | Delete a list. Local server only. |
419
- | `delete_list_item` | destructive | Delete an item. Local server only. |
420
-
421
- ### Family Tools
422
-
423
- | Tool | Effect | Description |
424
- |------|--------|-------------|
425
- | `get_family_members` | read | Family member profiles, with the IDs and names that `to` and `profiles` use |
426
- | `get_frame_info` | read | Household/frame information |
427
- | `get_devices` | read | Skylight devices |
428
- | `get_avatars` | read | Available profile avatars |
429
- | `get_colors` | read | Available profile colours |
430
-
431
- ### Meal Tools
432
-
433
- Meals and recipes need Skylight Plus.
434
-
435
- | Tool | Effect | Description |
436
- |------|--------|-------------|
437
- | `get_meal_categories` | read | Breakfast, Lunch, Dinner, Snack and their IDs |
438
- | `get_meal_sittings` | read | Scheduled meals in a date range, with sitting IDs |
439
- | `create_meal_sitting` | write | Schedule a meal on a date (free text or a saved recipe) |
440
- | `update_meal_sitting` | write | Rename, re-categorise, add a note to, or move a one-off meal, by sitting ID and its current date. Repeating meals are refused. |
441
- | `delete_meal_sitting` | destructive | Delete a one-off meal. Local server only. Previews unless `confirm: true`; reports success only when Skylight confirms the removal. |
442
- | `get_recipes` / `get_recipe` | read | Browse saved recipes |
443
- | `create_recipe` / `update_recipe` | write | Add or change a recipe |
444
- | `delete_recipe` | destructive | Delete a recipe. Local server only. |
445
- | `add_recipe_to_grocery_list` | write | Push a recipe's ingredients to the grocery list |
446
-
447
- ### Reward Tools
448
-
449
- Rewards need Skylight Plus.
450
-
451
- | Tool | Effect | Description |
452
- |------|--------|-------------|
453
- | `get_rewards` | read | Available rewards |
454
- | `get_reward_points` | read | Reward points balance |
455
- | `create_reward` | write | Add a reward |
456
- | `update_reward` | write | Change a reward |
457
- | `redeem_reward` | write | Redeem a reward, spending points |
458
- | `unredeem_reward` | write | Undo a redemption |
459
- | `delete_reward` | destructive | Delete a reward. Local server only. |
460
-
461
- ### Photo Tools
462
-
463
- Needs Skylight Plus. Local server only.
464
-
465
- | Tool | Effect | Description |
466
- |------|--------|-------------|
467
- | `get_albums` | read | Photo albums |
468
-
469
- ## Skylight API calls used
470
-
471
- 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.
472
-
473
- | Function | Calls |
474
- |----------|-------|
475
- | Calendar | `GET/POST /frames/{f}/calendar_events` (live), `PUT /calendar_events/{id}`, `DELETE /calendar_events/{id}` (live), `GET /frames/{f}/source_calendars` (live) |
476
- | Messages | `GET /frames/{f}/nudges?after&before`, `POST /nudges` (everyone or `category_ids`), `PATCH /nudges/{id}`, `DELETE /nudges/{id}` (all live) |
477
- | Meals | `GET /meals/categories`, `GET/POST /meals/sittings`, `PATCH/DELETE /meals/sittings/{id}/instances/{date}` (all live) |
478
- | Recipes | `GET/POST /meals/recipes`, `GET/PATCH/DELETE /meals/recipes/{id}`, `POST /meals/recipes/{id}/add_to_grocery_list` |
479
- | Profiles and family | `GET /frames/{f}/categories` (live), `GET /frames/{f}`, `GET /frames/{f}/devices`, `GET /api/avatars`, `GET /api/colors` |
480
- | Chores and tasks | `GET/POST /frames/{f}/chores`, `PUT/DELETE /chores/{id}`, `POST /frames/{f}/task_box/items` |
481
- | Lists | `GET/POST /lists`, `GET/PUT/DELETE /lists/{id}`, `POST /lists/{id}/list_items`, `PUT/DELETE /lists/{id}/list_items/{itemId}` |
482
- | Rewards | `GET/POST /rewards`, `PATCH/DELETE /rewards/{id}`, `POST /rewards/{id}/redeem`, `POST /rewards/{id}/unredeem`, `GET /reward_points` |
483
- | Photos | `GET /frames/{f}/albums` (local only) |
484
-
485
- The planning tools make no calls of their own; they combine the calendar, chore, meal and list calls above.
486
-
487
- ## Example Queries
488
-
489
- Once configured, you can ask Claude things like:
490
-
491
- - "What's on my calendar today?"
492
- - "What chores do I need to do this week?"
493
- - "Add 'take out trash' to my chores for tomorrow"
494
- - "What's on the grocery list?"
495
- - "Add milk to my task list"
496
- - "Who are the family members on Skylight?"
497
- - "How many reward points does each person have?"
498
-
499
- ## Development
500
-
501
- ```bash
502
- # Run in development mode (with hot reload)
503
- npm run dev
504
-
505
- # Build
506
- npm run build
507
-
508
- # Run tests
509
- npm test
510
-
511
- # Type check
512
- npm run typecheck
513
- ```
514
-
515
- ### Live tests
516
-
517
- `npm test` uses mocked network calls and is what CI runs. A separate, opt-in
518
- suite runs the real tools against your own Skylight frame to catch drift in
519
- Skylight's API: read smoke checks, response-shape contract checks, and a
520
- create/edit/move/delete round trip on a throwaway meal. It does nothing unless
521
- you set `SKYLIGHT_LIVE=1`, never sends messages, and cleans up after itself.
522
- Each test is one file in `tests/live/`, so adding or removing one is adding or
523
- deleting a file. See [tests/live/README.md](tests/live/README.md).
524
-
525
- ```bash
526
- SKYLIGHT_FRAME_ID=your_frame_id SKYLIGHT_TIMEZONE=America/Los_Angeles \
527
- SKYLIGHT_LIVE=1 npm run test:live
528
- ```
529
-
530
- Set `SKYLIGHT_TIMEZONE` to your frame's zone. It defaults to `America/New_York`,
531
- which shifts every date window the suite asks for if your frame is elsewhere.
532
- Sign in first with `skylight-mcp auth login`.
533
-
534
- ## API Documentation
535
-
536
- This MCP server is built on top of the reverse-engineered Skylight API. The API endpoints were documented using the [skylight-api](https://github.com/TheEagleByte/skylight-api) project, which converts browser network traffic (HAR files) into an OpenAPI specification.
537
-
538
- **API Resources:**
539
- - [Interactive API Docs (Swagger UI)](https://theeaglebyte.github.io/skylight-api/swagger.html)
540
- - [API Reference (ReDoc)](https://theeaglebyte.github.io/skylight-api/redoc.html)
541
- - [OpenAPI Specification](https://theeaglebyte.github.io/skylight-api/openapi/openapi.yaml)
542
-
543
- If you discover new API endpoints or find issues with the current documentation, please contribute to the [skylight-api](https://github.com/TheEagleByte/skylight-api) repository.
544
-
545
- ## Contributing
546
-
547
- Contributions are welcome! Here's how you can help:
548
-
549
- 1. **Fork the repository** and create a feature branch
550
- 2. **Make your changes** with clear, descriptive commits
551
- 3. **Run tests** (`npm test`) and linting (`npm run lint`) before submitting
552
- 4. **Open a pull request** with a description of your changes
553
-
554
- ### Development Setup
555
-
556
- ```bash
557
- git clone https://github.com/TheEagleByte/skylight-mcp.git
558
- cd skylight-mcp
559
- npm install
560
- npm run dev # Start with hot reload
561
- ```
562
-
563
- ### Areas for Contribution
564
-
565
- - Adding support for new Skylight API endpoints
566
- - Improving error handling and edge cases
567
- - Enhancing documentation
568
- - Writing additional tests
569
-
570
- ## Issues & Support
571
-
572
- - **Bug reports**: [Open an issue](https://github.com/TheEagleByte/skylight-mcp/issues/new) with steps to reproduce
573
- - **Feature requests**: [Open an issue](https://github.com/TheEagleByte/skylight-mcp/issues/new) describing the use case
574
- - **Questions**: [Start a discussion](https://github.com/TheEagleByte/skylight-mcp/discussions) or open an issue
575
-
576
- Please include relevant details like your Node.js version, error messages, and configuration (with sensitive values redacted).
577
-
578
- ## License
579
-
580
- MIT
581
-
582
- ## Disclaimer
583
-
584
- This is an unofficial integration. The Skylight API is reverse-engineered and may change without notice. Use at your own risk.
1
+ # Skylight MCP Server
2
+
3
+ An MCP (Model Context Protocol) server for the Skylight Calendar. Lets Claude
4
+ (including Claude Duet, via the hosted mode) read and plan against your family
5
+ calendar, chores, lists, meals and rewards.
6
+
7
+ > **Unofficial.** This project is not affiliated with or endorsed by Skylight.
8
+ > Skylight has no public API; this talks to the same private endpoints their
9
+ > own apps use, discovered by observing app traffic. It can stop working or be
10
+ > blocked at any time, without notice. Use it with your own account, at your
11
+ > own risk, and keep request volume modest.
12
+ >
13
+ > Forked from [TheEagleByte/skylight-mcp](https://github.com/TheEagleByte/skylight-mcp)
14
+ > (MIT). The sign-in and credential-store design follows
15
+ > [kamilio/skylight-calendar-agent](https://github.com/kamilio/skylight-calendar-agent)
16
+ > (MIT). See `THIRD_PARTY_NOTICES.md`.
17
+
18
+ ## Features
19
+
20
+ - **Calendar**: read events and connected calendars; create, update and delete events
21
+ - **Planning**: one-call week overview, overlap detection, free-slot search, bulk meal planning
22
+ - **Meals** (Plus): see, add, rename, move and delete scheduled meals; browse and edit recipes
23
+ - **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_message` but was not tested live).
24
+ - **Chores and tasks**: read, create, update and delete chores; add to the task box
25
+ - **Lists**: read, create, update and delete lists and items
26
+ - **Family**: read profiles, devices and household info
27
+ - **Rewards** (Plus): read points and rewards; create, update, redeem and delete
28
+
29
+ See [What is supported](#what-is-supported) for what is tested, what is not, and
30
+ what the hosted server leaves out.
31
+
32
+ ## Quick Start
33
+
34
+ You need Node 20+, a Skylight account, and an MCP client (Claude desktop, Claude
35
+ Code, or any other). Nothing to clone or build.
36
+
37
+ **Do I need the hosted server?** Probably not. The steps below give you the
38
+ *local* server: it runs on your own computer while your MCP client is open and
39
+ exposes every tool. The *hosted* server (a Cloudflare Worker) is optional. You
40
+ only need it if Claude Duet or Claude on another device has to reach Skylight
41
+ when this computer is off. It means cloning the repo, a free Cloudflare
42
+ account, `npx wrangler login`, `npm run worker:setup`, and a second sign-in,
43
+ and it exposes only read and write tools plus `delete_message`. Do the local
44
+ setup first; see
45
+ [Hosted server (Claude Duet, Claude on other devices)](#hosted-server-claude-duet-claude-on-other-devices)
46
+ if you need it later.
47
+
48
+ ### 1. Find your frame ID and timezone
49
+
50
+ Sign in to the Skylight web app at https://ourskylight.com. Your frame ID is the
51
+ number in the address bar, `https://ourskylight.com/my-skylight/<number>` (see
52
+ [Finding your Frame ID](#finding-your-frame-id)). Your timezone is an IANA name
53
+ such as `America/Chicago`. **Set it**: it defaults to `America/New_York`, and
54
+ event times and date windows come out wrong anywhere else.
55
+
56
+ ### 2. Set them in your terminal
57
+
58
+ These last only for the terminal window you set them in. Your MCP client does
59
+ not inherit them; step 5 puts them in its config.
60
+
61
+ macOS / Linux:
62
+ ```bash
63
+ export SKYLIGHT_FRAME_ID=your_frame_id
64
+ export SKYLIGHT_TIMEZONE=America/New_York
65
+ ```
66
+
67
+ Windows PowerShell:
68
+ ```powershell
69
+ $env:SKYLIGHT_FRAME_ID="your_frame_id"
70
+ $env:SKYLIGHT_TIMEZONE="America/New_York"
71
+ ```
72
+
73
+ ### 3. Sign in (once)
74
+
75
+ ```bash
76
+ npx -y @davidwoods/skylight-mcp auth login
77
+ ```
78
+
79
+ This prints a Skylight sign-in URL. Open it in your browser and sign in (in Windows
80
+ Terminal, hold Ctrl and click the URL). The
81
+ browser bounces through `https://ourskylight.com/welcome?code=...` and on to
82
+ the Skylight home page too fast to copy. Get it from your browser history:
83
+ `Cmd-Y` (Mac) / `Ctrl-H` (PC), search `welcome?code`, right-click the Skylight
84
+ result dated today (top of the list; older ones are earlier sign-ins and will not
85
+ work), Copy link address. Then:
86
+
87
+ ```bash
88
+ npx -y @davidwoods/skylight-mcp auth complete
89
+ ```
90
+
91
+ and paste it. Your password never touches this program; only the resulting
92
+ OAuth tokens are stored, encrypted with a key in the macOS Keychain (plain
93
+ `0600` file on Linux/Windows). macOS may ask once to allow Keychain access.
94
+ `auth status` and `auth logout` do what they say.
95
+
96
+ ### 4. Check it
97
+
98
+ ```bash
99
+ npx -y @davidwoods/skylight-mcp doctor
100
+ ```
101
+
102
+ `doctor` checks Node, the frame ID, the timezone, your sign-in and one real call
103
+ to Skylight, and says what to fix. It never prints tokens or your frame ID.
104
+ Add `--offline` to skip the network call. Do this before step 5: a client that
105
+ fails to start gives you much less to go on than `doctor` does.
106
+
107
+ ### 5. Add to your MCP client
108
+
109
+ **Claude desktop** (Settings → Developer → Edit Config), or any client that uses `mcp.json`:
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "skylight": {
114
+ "command": "npx",
115
+ "args": ["-y", "@davidwoods/skylight-mcp"],
116
+ "env": {
117
+ "SKYLIGHT_FRAME_ID": "your_frame_id",
118
+ "SKYLIGHT_TIMEZONE": "America/New_York"
119
+ }
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ **Claude Code:**
126
+ ```bash
127
+ claude mcp add skylight \
128
+ -e SKYLIGHT_FRAME_ID=your_frame_id -e SKYLIGHT_TIMEZONE=America/New_York \
129
+ -- npx -y @davidwoods/skylight-mcp
130
+ ```
131
+
132
+ Restart the client fully (Cmd-Q on Mac), then ask it "what's on the family
133
+ calendar this week?"
134
+
135
+ ### From source (contributors)
136
+
137
+ ```bash
138
+ git clone https://github.com/dwooods/skylight-mcp.git
139
+ cd skylight-mcp && npm install && npm run build
140
+ node dist/index.js auth login # same steps as above, using node dist/index.js
141
+ ```
142
+
143
+ In the client config use `"command": "node", "args": ["/path/to/skylight-mcp/dist/index.js"]`.
144
+
145
+ ### Windows (partly verified)
146
+
147
+ Verified on one Windows machine (Node 24): `npx` runs the package, `doctor --offline`
148
+ works, `auth login` and `auth complete` finish the real sign-in, the credential is
149
+ saved to `%APPDATA%\skylight-mcp\credential.json`, and the Claude desktop app runs the
150
+ server from the config below and reads real calendar data. **Not yet verified:**
151
+ `doctor`'s Skylight check on Windows (it was broken on every platform in 1.7.0 and is
152
+ fixed in 1.8.0), the live test suite, and `worker:setup`.
153
+
154
+ - `npx` is a `.cmd` file on Windows, so in a client config use
155
+ `"command": "cmd", "args": ["/c", "npx", "-y", "@davidwoods/skylight-mcp"]`.
156
+ - Set variables in PowerShell like this, then run the command on the next line:
157
+ `$env:SKYLIGHT_FRAME_ID="your_frame_id"; $env:SKYLIGHT_TIMEZONE="America/New_York"`.
158
+ - In Windows Terminal you can hold Ctrl and click the sign-in URL. In the history
159
+ search, right-click the result dated today and choose Copy link address.
160
+ - The sign-in is stored **unencrypted** in `%APPDATA%\skylight-mcp\credential.json`.
161
+ There is no Keychain equivalent wired in, and Windows ignores Unix `0600`
162
+ permissions. Only your Windows user profile protects it.
163
+
164
+ ### Troubleshooting
165
+
166
+ | Symptom | Likely cause and fix |
167
+ |---|---|
168
+ | Server fails to start, mentions configuration | `SKYLIGHT_FRAME_ID` missing. Run `doctor`. |
169
+ | "Not signed in" | Run `auth login` then `auth complete`. Logins expire after 10 minutes. |
170
+ | macOS Keychain error | The server refuses to fall back to plaintext. Unlock the Keychain or allow access when prompted. |
171
+ | Events show at the wrong hour | `SKYLIGHT_TIMEZONE` unset (defaults to New York). Set your zone. |
172
+ | 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". |
173
+ | Worked yesterday, fails today | Skylight has no public API and may have changed. Run `doctor`, then check the issues. |
174
+
175
+ ### First five things to ask Claude
176
+
177
+ 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.
178
+
179
+ 1. **"What's on our family calendar this week?"** Reads events (`get_calendar_events`). If times are off by hours, your `SKYLIGHT_TIMEZONE` is wrong.
180
+ 2. **"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`).
181
+ 3. **"When are we all free for 90 minutes on Saturday?"** Finds gaps around the fixed events (`find_free_slots`).
182
+ 4. **"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.
183
+ 5. **"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.
184
+
185
+ If a tool you expected is missing, see the [Troubleshooting](#troubleshooting) table, and check "What is supported" for tools that are local-only or Plus-only.
186
+
187
+ ### Instructions for AI
188
+
189
+ Copy this into your AI's custom instructions or system prompt:
190
+
191
+ > 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.
192
+ >
193
+ > Tips:
194
+ > - Call `get_family_members` before assigning chores to get member names
195
+ > - Grocery items default to the main grocery list if no list specified
196
+ > - Dates accept "today", "tomorrow", day names, or YYYY-MM-DD format
197
+ > - Some tools (rewards, meals, photos) require Skylight Plus subscription
198
+
199
+ ## Prerequisites
200
+
201
+ - Node.js 20+
202
+ - A Skylight account with an active subscription
203
+ - Your Skylight Frame ID (see [Finding your Frame ID](#finding-your-frame-id))
204
+
205
+ ## Authentication
206
+
207
+ Sources are tried in this order:
208
+
209
+ 1. **`SKYLIGHT_TOKEN`** - an explicit token in the environment, if set.
210
+ 2. **Stored OAuth credential** (recommended) - created by `skylight-mcp auth login`
211
+ / `auth complete`. The access token is refreshed automatically; refresh tokens
212
+ rotate and the rotated token is persisted.
213
+ 3. **`SKYLIGHT_EMAIL` + `SKYLIGHT_PASSWORD`** (deprecated) - the server replays
214
+ Skylight's web login form itself. This works but puts your password in your
215
+ MCP client config in plaintext. Prefer the browser sign-in above.
216
+
217
+ If none is present the server fails with instructions rather than starting.
218
+
219
+ Where the credential lives:
220
+
221
+ | Platform | Location | Protection |
222
+ |----------|----------|------------|
223
+ | macOS | `~/Library/Application Support/skylight-mcp/credential.enc.json` | AES-256-GCM; key in Keychain (`skylight-mcp.encryption-key`) |
224
+ | Linux / other | `$XDG_CONFIG_HOME/skylight-mcp/credential.json` (default `~/.config/skylight-mcp/`) | `0600` file in a `0700` directory, no encryption |
225
+ | Windows | `%APPDATA%\\skylight-mcp\\credential.json` | Not encrypted. Windows ignores `0600`; only your user profile's own permissions protect it. Untested, see below. |
226
+
227
+ If the Keychain is unavailable on macOS the server refuses to read or write the
228
+ credential; it never silently downgrades to plaintext.
229
+
230
+ ### Finding your Frame ID
231
+
232
+ The frame ID is the household identifier. The easy way: after signing in, the
233
+ Skylight web app lands on `https://ourskylight.com/my-skylight/<number>`. That
234
+ number is your frame ID. Treat it as a secret.
235
+
236
+ The hard way, if that URL doesn't appear:
237
+
238
+ 1. Use a proxy tool ([Proxyman](https://proxyman.io/), [Charles](https://www.charlesproxy.com/), or [mitmproxy](https://mitmproxy.org/))
239
+ 2. Capture any API request from the Skylight app
240
+ 3. Look at the URL path: `/api/frames/{frameId}/...`
241
+ 4. Example: `/api/frames/abc123/chores` → frame ID is `abc123`
242
+
243
+ ## Configuration
244
+
245
+ | Variable | Required | Description |
246
+ |----------|----------|-------------|
247
+ | `SKYLIGHT_TOKEN` | No | Explicit API token; overrides the stored credential |
248
+ | `SKYLIGHT_AUTH_TYPE` | No | `bearer` (default) or `basic` (only with `SKYLIGHT_TOKEN`) |
249
+ | `SKYLIGHT_EMAIL` | No (deprecated) | Fallback email/password login |
250
+ | `SKYLIGHT_PASSWORD` | No (deprecated) | Fallback email/password login |
251
+ | `SKYLIGHT_FRAME_ID` | Yes | Your household frame ID |
252
+ | `SKYLIGHT_TIMEZONE` | No | Default timezone (default: `America/New_York`) |
253
+
254
+ ### Auth Notes
255
+
256
+ - The API host is pinned to `https://app.ourskylight.com`. No environment
257
+ variable or `.env` file can redirect requests (and the bearer token) elsewhere.
258
+ - `auth logout` deletes the local credential only; Skylight has no public way to
259
+ revoke a session, so change your password if a credential may have leaked.
260
+ - Skylight has no official API. This uses the same OAuth client the Skylight
261
+ app uses, and it may break or be blocked at any time.
262
+
263
+ ## Hosted server (Claude Duet, Claude on other devices)
264
+
265
+ The same code also runs as a remote MCP server on Cloudflare Workers, which is
266
+ how Claude Duet (and any Claude client that can't reach your Mac) connects.
267
+ One Worker is both the OAuth authorization server Claude talks to and the MCP
268
+ resource server. Signing in is the same browser-paste flow as the CLI; your
269
+ Skylight password never reaches the Worker, and the Skylight tokens are kept
270
+ encrypted in the OAuth grant, not in a database you manage.
271
+
272
+ ### Which tools the hosted server exposes, and why
273
+
274
+ By default the hosted server registers **read and create/update tools only**:
275
+ no `delete_*` tools and no photo uploads. The local (stdio) server registers
276
+ everything. This is a deliberate choice, not a limitation of the Worker:
277
+
278
+ - The hosted server is what Claude Duet drives proactively, sometimes
279
+ unattended. A misread instruction that *creates* a wrong chore is a
280
+ nuisance; one that *deletes* a calendar event is data loss with no undo
281
+ from Claude. Leaving deletes off the hosted path caps the worst outcome at
282
+ "something extra to clean up".
283
+ - Photo tools are uploads the Worker has no reason to carry.
284
+
285
+ If you want full parity on the hosted server, change one line in
286
+ `src/worker/mcp.ts` and redeploy:
287
+
288
+ ```ts
289
+ export const HOSTED_EFFECTS: readonly ToolEffect[] = ["read", "write", "destructive"];
290
+ ```
291
+
292
+ and set `photos: true` in the same file if you also want photo uploads.
293
+ Every tool's classification lives in `src/tools/registry.ts`, so you can also
294
+ carve out a profile of your own (read-only, for example, is `["read"]`).
295
+
296
+ If you run both servers in the Claude desktop app, Claude sees two sets of
297
+ mostly duplicate tools and may use either for writes. Disable the local one
298
+ in Settings → Connectors when you do not need the delete or photo tools, or
299
+ say "use the local Skylight server" when you do.
300
+
301
+ ### Deploy (once)
302
+
303
+ You need a free Cloudflare account. From a clone of this repo:
304
+
305
+ ```bash
306
+ npm install
307
+ npx wrangler login
308
+ npm run worker:setup # add -- --dry-run first to see the steps
309
+ ```
310
+
311
+ `worker:setup` creates your KV namespace, writes a gitignored
312
+ `wrangler.local.jsonc` with your real ID (the committed `wrangler.jsonc` keeps a
313
+ placeholder, so nothing account-specific can be committed), asks you for a
314
+ sign-in passphrase, deploys, sets `WORKER_URL`, deploys again and prints the
315
+ connector URL. Re-deploy later with `npm run worker:deploy:local`.
316
+
317
+ Doing it by hand instead:
318
+
319
+ ```bash
320
+ cp wrangler.jsonc wrangler.local.jsonc # gitignored: your real IDs live only here
321
+ npx wrangler kv namespace create OAUTH_KV # paste the id into wrangler.local.jsonc, not wrangler.jsonc
322
+ npx wrangler secret put SETUP_PASSPHRASE -c wrangler.local.jsonc # 12+ chars; required to link an account
323
+ npm run worker:deploy:local # prints the Worker's URL
324
+ npx wrangler secret put WORKER_URL -c wrangler.local.jsonc # paste that URL (origin only, no path)
325
+ npm run worker:deploy:local
326
+ ```
327
+
328
+ `WORKER_URL` must be exactly the public origin of the Worker; every token is
329
+ bound to it. It is a secret rather than a `vars` entry so the server's
330
+ address is not published with the source.
331
+
332
+ ### Connect from Claude
333
+
334
+ In Claude (or Duet) → Settings → Connectors → Add custom connector:
335
+
336
+ - Name: `Skylight`
337
+ - URL: `https://skylight-mcp.<your-subdomain>.workers.dev/mcp`
338
+
339
+ Claude opens the consent page. Enter the setup passphrase, your frame ID and
340
+ timezone, then follow the two-step Skylight sign-in and paste the redirect
341
+ URL. Connectors are shared across your Claude account, so Duet sees it too.
342
+
343
+ ### What is stored where
344
+
345
+ | Thing | Where | Protection |
346
+ |---|---|---|
347
+ | Skylight access + refresh tokens | OAuth grant props in `OAUTH_KV` | Encrypted by workers-oauth-provider; the key is derived from the token Claude holds, so KV alone is not enough |
348
+ | PKCE verifier during sign-in | Upstream transaction in `OAUTH_KV` | Bound to your browser by a `__Host-` cookie; single use; 10-minute TTL |
349
+ | Setup passphrase | Worker secret | Never in the repo or `wrangler.jsonc` |
350
+ | Nothing else | | The Worker has no database and no logs of tokens |
351
+
352
+ To revoke: remove the connector in Claude (revokes the grant), or
353
+ `npx wrangler kv key list --binding OAUTH_KV` and delete, or change your
354
+ Skylight password.
355
+
356
+ ## What is supported
357
+
358
+ Status vocabulary, used honestly:
359
+
360
+ - **Live-tested**: run against a real Skylight frame by this project, with the result checked by reading back.
361
+ - **Inherited**: came from the upstream project and works there, but this project has not re-run it live.
362
+ - **Mocked only**: covered by this project's mocked tests; not yet run against real data.
363
+
364
+ Skylight has no public API. Everything below can break when Skylight ships a
365
+ web release, which happens without notice. The web app version and API version
366
+ last captured are recorded in the repository's build log.
367
+
368
+ | Area | What works | Hosted server (Claude Duet) | Status |
369
+ |------|------------|------------------------------|--------|
370
+ | Calendar | Read events and source calendars; create (with profiles), update, delete events | Everything except delete | Read, create and delete live-tested; update inherited |
371
+ | Planning | Week overview, conflicts, free slots, bulk meal plan | All four | Week overview live-tested; the rest mocked only |
372
+ | Meals (Plus) | Read categories and meals; create, rename, move, delete one-off meals | Everything except delete | Live-tested |
373
+ | Recipes (Plus) | Read, create, update, delete recipes; push ingredients to the grocery list | Everything except delete | Inherited |
374
+ | 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 |
375
+ | Chores | Read, create, update, delete | Everything except delete | Read live-tested; writes inherited |
376
+ | Task box | Add a task | Yes | Inherited |
377
+ | Lists | Read lists and items; create, update, delete lists and items | Everything except delete | Inherited |
378
+ | Family and devices | Profiles with IDs, avatars, colours, household info, devices | Yes | Profiles live-tested; the rest inherited |
379
+ | Rewards (Plus) | Read points and rewards; create, update, redeem, unredeem, delete | Everything except delete | Inherited |
380
+ | Photos (Plus) | List albums | No | Inherited |
381
+
382
+ ### Not supported
383
+
384
+ Nothing here is hidden behind a flag. It is either unavailable from Skylight or not yet captured.
385
+
386
+ - **Messages**: deleting one (Skylight offers no way), sending to specific profiles or devices, scheduling, repeating, voice notes, and read-aloud (the option exists on `send_message` but was not tested live).
387
+ - **Meals**: editing or deleting a repeating meal (it is refused, because the behaviour of changing one occurrence of a series was not captured), saving a meal to the recipe box from the edit form, and Skylight's own "Plan Meals" feature.
388
+ - **Task box**: listing, editing or completing tasks. It is create-only.
389
+ - **Profiles**: creating or editing them. Read-only.
390
+ - **Settings, account, notifications, sleep schedule, device navigation**: none of these are exposed.
391
+ - **Photos**: uploads and anything beyond listing albums.
392
+ - **Accounts with more than one frame**: the server is pinned to one frame ID.
393
+ - **Credentials**: sign-in, sign-out and token access are CLI commands (`skylight-mcp auth ...`) and are deliberately not MCP tools.
394
+
395
+ ### Keeping this honest
396
+
397
+ Every tool must appear in the tables below with the effect it really has, and a
398
+ test fails if one is missing or mislabelled. When you add, change or remove a
399
+ tool, update this section in the same change.
400
+
401
+ ## Available Tools
402
+
403
+ Effects: **read** changes nothing; **write** creates or edits and can be edited
404
+ again; **destructive** cannot be undone from the API. The hosted server registers
405
+ read and write only, so destructive tools exist on the local server alone.
406
+
407
+ ### Planning Tools
408
+
409
+ Built for "what does the week look like and what is for dinner" questions.
410
+ One call returns calendar, chores/tasks, meals and lists, merged per day.
411
+
412
+ | Tool | Effect | Description |
413
+ |------|--------|-------------|
414
+ | `get_week_overview` | 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. |
415
+ | `find_conflicts` | read | Timed events that overlap in a date range. All-day events never conflict. |
416
+ | `find_free_slots` | read | Gaps of at least N minutes inside a daily window (default 08:00 to 21:00). |
417
+ | `plan_week_meals` | write | Fill several meal slots at once. `dryRun` previews; occupied slots are skipped, never overwritten; all entries are validated before anything is written; optional grocery push for recipes. |
418
+
419
+ Times are computed in the frame's time zone, including DST changes.
420
+
421
+ ### Message Tools
422
+
423
+ Messages are the notes shown on the frame (Skylight's web app calls them "nudges").
424
+
425
+ | Tool | Effect | Description |
426
+ |------|--------|-------------|
427
+ | `get_messages` | read | Messages in a date range with time, text, recipients and sender. Never returns the presigned audio URLs. |
428
+ | `send_message` | write | Post a message to the frame now, on all devices. Previews by default and posts only with `confirm: true`. Read aloud is **off** unless asked for. `to` names the profiles to send to (see `get_family_members`); omit it for everyone. Optional sender profile. Up to 300 characters. The app has no delete button, but `delete_message` removes one. |
429
+ | `update_message` | 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. |
430
+ | `delete_message` | destructive | Delete a message. The app has no delete button; this uses `DELETE /nudges/{id}`, documented by other Skylight clients and verified on a real frame. Available on both the local and hosted servers (the one destructive tool the hosted server offers). Previews unless `confirm: true`, then reads back and reports success only if the message is gone. |
431
+
432
+ ### Calendar Tools
433
+
434
+ | Tool | Effect | Description |
435
+ |------|--------|-------------|
436
+ | `get_calendar_events` | read | Events for a date range |
437
+ | `get_source_calendars` | read | Connected calendar sources (Google, iCloud, etc.) |
438
+ | `create_calendar_event` | write | Add an event. `profiles` assigns it to named family members. Local times are sent with the frame's UTC offset. |
439
+ | `update_calendar_event` | write | Change an event, including who it is assigned to (`profiles`) |
440
+ | `delete_calendar_event` | destructive | Delete an event. Local server only. |
441
+
442
+ ### Chore Tools
443
+
444
+ | Tool | Effect | Description |
445
+ |------|--------|-------------|
446
+ | `get_chores` | read | Chores with optional filters (date, assignee, status) |
447
+ | `create_chore` | write | Create a chore, optionally recurring |
448
+ | `update_chore` | write | Rename, reschedule, reassign, complete or reopen a chore |
449
+ | `delete_chore` | destructive | Delete a chore. Local server only. |
450
+
451
+ ### Task Tools
452
+
453
+ | Tool | Effect | Description |
454
+ |------|--------|-------------|
455
+ | `get_task_box` | read | List the task box: reusable chore and routine templates (no assignee or date) |
456
+ | `create_task` | write | Add a template to the task box |
457
+
458
+ ### List Tools
459
+
460
+ | Tool | Effect | Description |
461
+ |------|--------|-------------|
462
+ | `get_lists` | read | All lists |
463
+ | `get_list_items` | read | Items in one list |
464
+ | `create_list` | write | Create a list |
465
+ | `update_list` | write | Rename or change a list |
466
+ | `create_list_item` | write | Add an item |
467
+ | `update_list_item` | write | Edit or tick off an item |
468
+ | `delete_list` | destructive | Delete a list. Local server only. |
469
+ | `delete_list_item` | destructive | Delete an item. Local server only. |
470
+
471
+ ### Family Tools
472
+
473
+ | Tool | Effect | Description |
474
+ |------|--------|-------------|
475
+ | `get_family_members` | read | Family member profiles, with the IDs and names that `to` and `profiles` use |
476
+ | `get_frame_info` | read | Household/frame information |
477
+ | `get_devices` | read | Skylight devices |
478
+ | `get_avatars` | read | Available profile avatars |
479
+ | `get_colors` | read | Available profile colours |
480
+
481
+ ### Meal Tools
482
+
483
+ Meals and recipes need Skylight Plus.
484
+
485
+ | Tool | Effect | Description |
486
+ |------|--------|-------------|
487
+ | `get_meal_categories` | read | Breakfast, Lunch, Dinner, Snack and their IDs |
488
+ | `get_meal_sittings` | read | Scheduled meals in a date range, with sitting IDs |
489
+ | `create_meal_sitting` | write | Schedule a meal on a date (free text or a saved recipe) |
490
+ | `update_meal_sitting` | write | Rename, re-categorise, add a note to, or move a one-off meal, by sitting ID and its current date. Repeating meals are refused. |
491
+ | `delete_meal_sitting` | destructive | Delete a one-off meal. Local server only. Previews unless `confirm: true`; reports success only when Skylight confirms the removal. |
492
+ | `get_recipes` / `get_recipe` | read | Browse saved recipes |
493
+ | `create_recipe` / `update_recipe` | write | Add or change a recipe |
494
+ | `delete_recipe` | destructive | Delete a recipe. Local server only. |
495
+ | `add_recipe_to_grocery_list` | write | Push a recipe's ingredients to the grocery list |
496
+
497
+ ### Reward Tools
498
+
499
+ Rewards need Skylight Plus.
500
+
501
+ | Tool | Effect | Description |
502
+ |------|--------|-------------|
503
+ | `get_rewards` | read | Available rewards |
504
+ | `get_reward_points` | read | Reward points balance |
505
+ | `create_reward` | write | Add a reward |
506
+ | `update_reward` | write | Change a reward |
507
+ | `redeem_reward` | write | Redeem a reward, spending points |
508
+ | `unredeem_reward` | write | Undo a redemption |
509
+ | `delete_reward` | destructive | Delete a reward. Local server only. |
510
+
511
+ ### Photo Tools
512
+
513
+ Needs Skylight Plus. Local server only.
514
+
515
+ | Tool | Effect | Description |
516
+ |------|--------|-------------|
517
+ | `get_albums` | read | Photo albums |
518
+
519
+ ## Skylight API calls used
520
+
521
+ 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.
522
+
523
+ | Function | Calls |
524
+ |----------|-------|
525
+ | Calendar | `GET/POST /frames/{f}/calendar_events` (live), `PUT /calendar_events/{id}`, `DELETE /calendar_events/{id}` (live), `GET /frames/{f}/source_calendars` (live) |
526
+ | Messages | `GET /frames/{f}/nudges?after&before`, `POST /nudges` (everyone or `category_ids`), `PATCH /nudges/{id}`, `DELETE /nudges/{id}` (all live) |
527
+ | Meals | `GET /meals/categories`, `GET/POST /meals/sittings`, `PATCH/DELETE /meals/sittings/{id}/instances/{date}` (all live) |
528
+ | Recipes | `GET/POST /meals/recipes`, `GET/PATCH/DELETE /meals/recipes/{id}`, `POST /meals/recipes/{id}/add_to_grocery_list` |
529
+ | Profiles and family | `GET /frames/{f}/categories` (live), `GET /frames/{f}`, `GET /frames/{f}/devices`, `GET /api/avatars`, `GET /api/colors` |
530
+ | Chores and tasks | `GET/POST /frames/{f}/chores`, `PUT/DELETE /chores/{id}`, `POST /frames/{f}/task_box/items` |
531
+ | Lists | `GET/POST /lists`, `GET/PUT/DELETE /lists/{id}`, `POST /lists/{id}/list_items`, `PUT/DELETE /lists/{id}/list_items/{itemId}` |
532
+ | Rewards | `GET/POST /rewards`, `PATCH/DELETE /rewards/{id}`, `POST /rewards/{id}/redeem`, `POST /rewards/{id}/unredeem`, `GET /reward_points` |
533
+ | Photos | `GET /frames/{f}/albums` (local only) |
534
+
535
+ The planning tools make no calls of their own; they combine the calendar, chore, meal and list calls above.
536
+
537
+ ## Example Queries
538
+
539
+ Once configured, you can ask Claude things like:
540
+
541
+ - "What's on my calendar today?"
542
+ - "What chores do I need to do this week?"
543
+ - "Add 'take out trash' to my chores for tomorrow"
544
+ - "What's on the grocery list?"
545
+ - "Add milk to my task list"
546
+ - "Who are the family members on Skylight?"
547
+ - "How many reward points does each person have?"
548
+
549
+ ## Development
550
+
551
+ ```bash
552
+ # Run in development mode (with hot reload)
553
+ npm run dev
554
+
555
+ # Build
556
+ npm run build
557
+
558
+ # Run tests
559
+ npm test
560
+
561
+ # Type check
562
+ npm run typecheck
563
+ ```
564
+
565
+ ### Live tests
566
+
567
+ `npm test` uses mocked network calls and is what CI runs. A separate, opt-in
568
+ suite runs the real tools against your own Skylight frame to catch drift in
569
+ Skylight's API: read smoke checks, response-shape contract checks, and a
570
+ create/edit/move/delete round trip on a throwaway meal. It does nothing unless
571
+ you set `SKYLIGHT_LIVE=1`, never sends messages, and cleans up after itself.
572
+ Each test is one file in `tests/live/`, so adding or removing one is adding or
573
+ deleting a file. See [tests/live/README.md](tests/live/README.md).
574
+
575
+ ```bash
576
+ SKYLIGHT_FRAME_ID=your_frame_id SKYLIGHT_TIMEZONE=America/Los_Angeles \
577
+ SKYLIGHT_LIVE=1 npm run test:live
578
+ ```
579
+
580
+ Set `SKYLIGHT_TIMEZONE` to your frame's zone. It defaults to `America/New_York`,
581
+ which shifts every date window the suite asks for if your frame is elsewhere.
582
+ Sign in first with `skylight-mcp auth login`.
583
+
584
+ ## API Documentation
585
+
586
+ This MCP server is built on top of the reverse-engineered Skylight API. The API endpoints were documented using the [skylight-api](https://github.com/TheEagleByte/skylight-api) project, which converts browser network traffic (HAR files) into an OpenAPI specification.
587
+
588
+ **API Resources:**
589
+ - [Interactive API Docs (Swagger UI)](https://theeaglebyte.github.io/skylight-api/swagger.html)
590
+ - [API Reference (ReDoc)](https://theeaglebyte.github.io/skylight-api/redoc.html)
591
+ - [OpenAPI Specification](https://theeaglebyte.github.io/skylight-api/openapi/openapi.yaml)
592
+
593
+ If you discover new API endpoints or find issues with the current documentation, please contribute to the [skylight-api](https://github.com/TheEagleByte/skylight-api) repository.
594
+
595
+ ## Contributing
596
+
597
+ Contributions are welcome! Here's how you can help:
598
+
599
+ 1. **Fork the repository** and create a feature branch
600
+ 2. **Make your changes** with clear, descriptive commits
601
+ 3. **Run tests** (`npm test`) and linting (`npm run lint`) before submitting
602
+ 4. **Open a pull request** with a description of your changes
603
+
604
+ ### Development Setup
605
+
606
+ ```bash
607
+ git clone https://github.com/dwooods/skylight-mcp.git
608
+ cd skylight-mcp
609
+ npm install
610
+ npm run dev # Start with hot reload
611
+ ```
612
+
613
+ ### Areas for Contribution
614
+
615
+ - Adding support for new Skylight API endpoints
616
+ - Improving error handling and edge cases
617
+ - Enhancing documentation
618
+ - Writing additional tests
619
+
620
+ ## Issues & Support
621
+
622
+ See [SUPPORT.md](SUPPORT.md) for what to run first and what must never be pasted into an issue.
623
+
624
+ - **Bug reports and feature requests**: [open an issue](https://github.com/dwooods/skylight-mcp/issues/new/choose) and pick a template.
625
+ - **Security problems**: use private vulnerability reporting, not a public issue (see SUPPORT.md).
626
+
627
+ ## License
628
+
629
+ MIT
630
+
631
+ ## Disclaimer
632
+
633
+ This is an unofficial integration. The Skylight API is reverse-engineered and may change without notice. Use at your own risk.