circal-mcp 0.2.1 → 0.4.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.
- package/README.md +86 -28
- package/dist/circal-mcp.mjs +2122 -823
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
# circal-mcp
|
|
2
2
|
|
|
3
3
|
A stdio [MCP](https://modelcontextprotocol.io) server that reads and writes
|
|
4
|
-
|
|
5
|
-
in sync on disk, through the File System Access API on the web and the
|
|
6
|
-
shell's own bridge in the app (see `src/lib/mirror.ts`, `src/lib/mirrorLink.ts`
|
|
7
|
-
and `mac/main.swift`'s `MirrorFile`). It imports circal's own domain layer
|
|
4
|
+
the user's own circal calendar, through circal's own domain layer
|
|
8
5
|
(`src/lib/events.ts`, `recur.ts`, `quickadd.ts`, `mutate.ts`, `backup.ts`, …),
|
|
9
6
|
so an agent and the app can never disagree about what a legal event, an
|
|
10
7
|
occurrence, or a recurrence rule is.
|
|
11
8
|
|
|
9
|
+
Two transports, one tool table:
|
|
10
|
+
|
|
11
|
+
- **`--file <path>`** — the mirror file, the `.json` a browser tab or the
|
|
12
|
+
macOS app keeps in sync on disk (see `src/lib/mirror.ts`,
|
|
13
|
+
`src/lib/mirrorLink.ts` and `mac/main.swift`'s `MirrorFile`).
|
|
14
|
+
- **`--cloud`** — the cloud copy, the one encrypted Supabase row the app's
|
|
15
|
+
cloud sync writes (see `src/lib/cloudLink.ts`, `SYNC.md`). Sign in once
|
|
16
|
+
with `circal-mcp login`; every tool call then reads the row, decrypts it
|
|
17
|
+
**on this machine**, mutates, re-seals and swaps it back on `rev` — the
|
|
18
|
+
same compare-and-swap two browsers already use against each other, so the
|
|
19
|
+
server is simply the row's third client. Supabase still holds only
|
|
20
|
+
ciphertext: the derived key never leaves this machine, which is why this
|
|
21
|
+
server must run on the user's own hardware, never hosted.
|
|
22
|
+
|
|
12
23
|
Built on `@modelcontextprotocol/server` v2, implementing MCP protocol
|
|
13
24
|
revision 2026-07-28.
|
|
14
25
|
|
|
15
|
-
**The browser tab does not need to be open.** The mirror file
|
|
16
|
-
thing this server reads or writes; if circal's tab is open and
|
|
17
|
-
picks up the change
|
|
18
|
-
again, the same as any
|
|
26
|
+
**The browser tab does not need to be open.** The mirror file (or the row) is
|
|
27
|
+
the only thing this server reads or writes; if circal's tab is open and
|
|
28
|
+
connected, it picks up the change — instantly over Realtime for the cloud
|
|
29
|
+
copy — and its own next write moves the `rev` forward again, the same as any
|
|
30
|
+
second writer would.
|
|
19
31
|
|
|
20
32
|
## Quick start
|
|
21
33
|
|
|
@@ -34,21 +46,16 @@ zone, revision) plus a ready-to-paste config block. Paste that block into
|
|
|
34
46
|
your client below, swap the path if it picked up the wrong file, then
|
|
35
47
|
restart the client.
|
|
36
48
|
|
|
37
|
-
> **Use
|
|
38
|
-
> `
|
|
39
|
-
> `remindLead`, `organizer` and the guest pair on an
|
|
40
|
-
> `backupInterval`, `deviceZone`, `stampedZones` in
|
|
41
|
-
> round-trips the whole document through its
|
|
42
|
-
>
|
|
43
|
-
> refuses to write a
|
|
44
|
-
>
|
|
45
|
-
>
|
|
46
|
-
>
|
|
47
|
-
> pnpm build:mcp # -> mcp/dist/circal-mcp.mjs
|
|
48
|
-
> ```
|
|
49
|
-
>
|
|
50
|
-
> Then use `node /absolute/path/to/circal/mcp/dist/circal-mcp.mjs` wherever
|
|
51
|
-
> the examples below say `npx -y circal-mcp`.
|
|
49
|
+
> **Use `circal-mcp@0.3.0` or newer** (`npx -y circal-mcp` resolves it).
|
|
50
|
+
> The cautionary tale behind that floor: `0.1.0` predated nine fields in
|
|
51
|
+
> `src/lib` (`free`, `remindLead`, `organizer` and the guest pair on an
|
|
52
|
+
> event; `backupNudge`, `backupInterval`, `deviceZone`, `stampedZones` in
|
|
53
|
+
> settings), and every write round-trips the whole document through its
|
|
54
|
+
> reader, so it silently dropped all nine — measured, same fixture, two
|
|
55
|
+
> bundles. A current server refuses to write a document whose format is
|
|
56
|
+
> newer than its own, so a stale bundle can no longer damage one unnoticed;
|
|
57
|
+
> it will simply refuse. Contributors run against their own build with
|
|
58
|
+
> `pnpm build:mcp` → `node mcp/dist/circal-mcp.mjs`.
|
|
52
59
|
|
|
53
60
|
### Claude Desktop
|
|
54
61
|
|
|
@@ -103,6 +110,16 @@ No mirror file yet: on the Mac app there is almost always one already (`--find`
|
|
|
103
110
|
it); in a browser, turn the mirror on from circal's Settings panel, then run `--find` again. Run
|
|
104
111
|
`npx -y circal-mcp --help` for the full flag and environment variable list.
|
|
105
112
|
|
|
113
|
+
### Pinning a calendar
|
|
114
|
+
|
|
115
|
+
`--calendar <c>` (or `CIRCAL_CALENDAR`), beside either transport, pins the
|
|
116
|
+
connection's default calendar — an id or a unique name prefix. Every write
|
|
117
|
+
that names no `calendarId` lands there instead of on the user's default,
|
|
118
|
+
which is how an agent working one calendar stays on it. A create that was
|
|
119
|
+
neither named nor pinned says so in its own summary
|
|
120
|
+
(`calendar defaulted, not named`), so a wrong-calendar write is loud rather
|
|
121
|
+
than discovered by whoever gets the reminder.
|
|
122
|
+
|
|
106
123
|
## From source (contributors)
|
|
107
124
|
|
|
108
125
|
Building from a checkout instead of the published package:
|
|
@@ -117,6 +134,41 @@ Produces the same single-file ESM bundle at `mcp/dist/circal-mcp.mjs`.
|
|
|
117
134
|
`command`/`args` at `node` and the absolute path to that bundle instead of
|
|
118
135
|
`npx`, with the same `env`/`--file` as above.
|
|
119
136
|
|
|
137
|
+
## The cloud copy
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
circal-mcp login # email → sign-in code → passphrase; interactive
|
|
141
|
+
circal-mcp --cloud # serve against the row
|
|
142
|
+
circal-mcp logout # withdraw this machine's key, now
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`login` asks the site which Supabase project it uses (`https://circal.day/mcp.json`,
|
|
146
|
+
see `src/pages/mcp.json.ts`; `--site <url>` points a fork at its own
|
|
147
|
+
deployment, `--url`/`--key` name a project outright). Then three prompts:
|
|
148
|
+
email, the emailed sign-in code, the passphrase. The passphrase is proven
|
|
149
|
+
against the row's own box before anything is written, and what is left behind
|
|
150
|
+
is a `0600` record at the platform config dir
|
|
151
|
+
(`~/Library/Application Support/circal/cloud.json` on macOS;
|
|
152
|
+
`CIRCAL_CLOUD_CREDENTIALS` overrides) holding the session and the
|
|
153
|
+
**derived key — never the passphrase**. This is the browser's "stay
|
|
154
|
+
unlocked" record in Node clothes: same authority, same threat model, one
|
|
155
|
+
honest step weaker in that a file's bytes are readable where a
|
|
156
|
+
non-extractable `CryptoKey`'s are not — the price of a runtime with no
|
|
157
|
+
IndexedDB. Whoever can read that file can read the calendar; `logout`
|
|
158
|
+
deletes it immediately.
|
|
159
|
+
|
|
160
|
+
Sign-in is once per machine: the session renews itself, and each rotated
|
|
161
|
+
refresh token is persisted before it is spent. A browser's own session is
|
|
162
|
+
deliberately never borrowed — Supabase rotates refresh tokens with reuse
|
|
163
|
+
detection, so two clients sharing one session sign each other out.
|
|
164
|
+
|
|
165
|
+
The row must already exist: this server never mints the box. Enrolment — the
|
|
166
|
+
account, the passphrase, the first seal — belongs to the app's own Settings
|
|
167
|
+
panel, where the passphrase's meaning is explained before it is chosen.
|
|
168
|
+
|
|
169
|
+
Client config is the same as above with `--cloud` in place of
|
|
170
|
+
`--file <path>`.
|
|
171
|
+
|
|
120
172
|
## What it refuses, and why
|
|
121
173
|
|
|
122
174
|
- **No `CIRCAL_FILE` (or `--file <path>`).** The path is never guessed — a
|
|
@@ -134,8 +186,14 @@ Produces the same single-file ESM bundle at `mcp/dist/circal-mcp.mjs`.
|
|
|
134
186
|
- **A repeating event with no `scope`.** `update_event`, `delete_event` and
|
|
135
187
|
similar ask for `"one"`, `"future"`, or `"all"` before touching a series,
|
|
136
188
|
and name the event in the refusal rather than guessing.
|
|
137
|
-
- **A moved
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
revisions.
|
|
189
|
+
- **A moved document.** Every mutation reads, applies the change, and writes
|
|
190
|
+
`rev + 1` only if nothing else wrote in between — a confirm-read under an
|
|
191
|
+
advisory lock plus temp-file-and-rename for the file (so a crash mid-write
|
|
192
|
+
cannot truncate it), a conditional `update … where rev = N` for the row.
|
|
193
|
+
One retry against a fresh read, then a refusal naming both revisions.
|
|
194
|
+
- **Cloud only: an account with no row yet** (enrolment is the app's job, see
|
|
195
|
+
above), **a row re-sealed under a new passphrase** (the kept key cannot
|
|
196
|
+
open it; the refusal sends you back to `circal-mcp login`, and nothing is
|
|
197
|
+
written blind), and **a document one format version ahead** — a write here
|
|
198
|
+
round-trips the whole document, so an older server writing a newer file
|
|
199
|
+
would silently drop every field it has no name for. Reads still answer.
|