@davidwoods/skylight-mcp 1.7.0 → 1.8.1

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