@zykeco/sync-server 0.8.0 → 0.9.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 +35 -17
- package/dist/app.js +10 -0
- package/dist/app.js.map +1 -1
- package/dist/db/schema.d.ts +693 -0
- package/dist/db/schema.js +49 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/env.d.ts +10 -0
- package/dist/env.js +45 -6
- package/dist/env.js.map +1 -1
- package/dist/mcp/oauth.d.ts +48 -0
- package/dist/mcp/oauth.js +118 -1
- package/dist/mcp/oauth.js.map +1 -1
- package/dist/routes/activities.d.ts +3 -0
- package/dist/routes/activities.js +24 -0
- package/dist/routes/activities.js.map +1 -0
- package/dist/routes/activity-heart-rate.d.ts +3 -0
- package/dist/routes/activity-heart-rate.js +24 -0
- package/dist/routes/activity-heart-rate.js.map +1 -0
- package/dist/routes/mcp.js +44 -18
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/wipe.d.ts +3 -1
- package/dist/routes/wipe.js +5 -1
- package/dist/routes/wipe.js.map +1 -1
- package/dist/stores/activities.d.ts +3 -0
- package/dist/stores/activities.js +102 -0
- package/dist/stores/activities.js.map +1 -0
- package/dist/stores/activity-heart-rate.d.ts +3 -0
- package/dist/stores/activity-heart-rate.js +29 -0
- package/dist/stores/activity-heart-rate.js.map +1 -0
- package/drizzle/0003_real_kate_bishop.sql +49 -0
- package/drizzle/meta/0003_snapshot.json +1065 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -112,12 +112,15 @@ The server ships an [MCP](https://modelcontextprotocol.io) endpoint at `POST /mc
|
|
|
112
112
|
|
|
113
113
|
### Required env vars
|
|
114
114
|
|
|
115
|
-
| Var
|
|
116
|
-
|
|
|
117
|
-
| `MCP_ENABLED`
|
|
118
|
-
| `MCP_CLIENT_ID`
|
|
119
|
-
| `MCP_TOKEN_SECRET`
|
|
120
|
-
| `MCP_TOKEN_TTL_SECONDS`
|
|
115
|
+
| Var | Default | Purpose |
|
|
116
|
+
| ----------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `MCP_ENABLED` | `true` | Set `false` to disable the `/mcp` mount entirely. |
|
|
118
|
+
| `MCP_CLIENT_ID` | _req._ | Bootstrap client ID for the headless `client_credentials` grant. Public — pick anything memorable. |
|
|
119
|
+
| `MCP_TOKEN_SECRET` | _req._ | HMAC key for signing access + refresh tokens. **Must be ≥ 32 chars (enforced at startup)** — generate with `openssl rand -hex 32`. Treat as a secret. |
|
|
120
|
+
| `MCP_TOKEN_TTL_SECONDS` | `3600` | Bearer (access) token lifetime. |
|
|
121
|
+
| `MCP_REFRESH_ENABLED` | `true` | Issue refresh tokens on the auth-code grant so clients renew without re-login. `false` to disable. |
|
|
122
|
+
| `MCP_REFRESH_TTL_SECONDS` | `2592000` | Sliding refresh-token lifetime (30 d). Each refresh re-issues with a fresh window. |
|
|
123
|
+
| `MCP_REFRESH_MAX_TTL_SECONDS` | `7776000` | Absolute cap on a refresh chain from first issue (90 d). After this, re-auth is required. |
|
|
121
124
|
|
|
122
125
|
### HTTPS requirement & `PUBLIC_URL`
|
|
123
126
|
|
|
@@ -133,15 +136,15 @@ When `PUBLIC_URL` is set, it always wins. Without it, the server falls back to `
|
|
|
133
136
|
|
|
134
137
|
### Endpoints
|
|
135
138
|
|
|
136
|
-
| Method | Path | Purpose
|
|
137
|
-
| ------ | ----------------------------------------- |
|
|
138
|
-
| GET | `/.well-known/oauth-authorization-server` | RFC 8414 metadata (root-level, what MCP clients probe).
|
|
139
|
-
| GET | `/.well-known/oauth-protected-resource` | RFC 9728 resource metadata pointing back to the AS.
|
|
140
|
-
| POST | `/mcp/register` | Dynamic Client Registration (RFC 7591).
|
|
141
|
-
| GET | `/mcp/authorize` | Consent screen (PKCE flow).
|
|
142
|
-
| POST | `/mcp/authorize` | Consent form submit — issues an authorization code.
|
|
143
|
-
| POST | `/mcp/oauth/token` | Token endpoint (
|
|
144
|
-
| POST | `/mcp` | MCP JSON-RPC. Requires `Authorization: Bearer <token>`.
|
|
139
|
+
| Method | Path | Purpose |
|
|
140
|
+
| ------ | ----------------------------------------- | ----------------------------------------------------------------------------- |
|
|
141
|
+
| GET | `/.well-known/oauth-authorization-server` | RFC 8414 metadata (root-level, what MCP clients probe). |
|
|
142
|
+
| GET | `/.well-known/oauth-protected-resource` | RFC 9728 resource metadata pointing back to the AS. |
|
|
143
|
+
| POST | `/mcp/register` | Dynamic Client Registration (RFC 7591). |
|
|
144
|
+
| GET | `/mcp/authorize` | Consent screen (PKCE flow). |
|
|
145
|
+
| POST | `/mcp/authorize` | Consent form submit — issues an authorization code. |
|
|
146
|
+
| POST | `/mcp/oauth/token` | Token endpoint (`authorization_code`, `client_credentials`, `refresh_token`). |
|
|
147
|
+
| POST | `/mcp` | MCP JSON-RPC. Requires `Authorization: Bearer <token>`. |
|
|
145
148
|
|
|
146
149
|
### Connecting Claude.ai (web or desktop) as a custom connector
|
|
147
150
|
|
|
@@ -168,8 +171,9 @@ That means it's hitting the host root instead of the discovered path. Two causes
|
|
|
168
171
|
|
|
169
172
|
#### Token / client lifetime
|
|
170
173
|
|
|
171
|
-
- Bearer tokens expire after `MCP_TOKEN_TTL_SECONDS` (default 1 h).
|
|
172
|
-
-
|
|
174
|
+
- Bearer (access) tokens expire after `MCP_TOKEN_TTL_SECONDS` (default 1 h).
|
|
175
|
+
- When `MCP_REFRESH_ENABLED` is on (default), the auth-code grant also returns a **refresh token**. Clients (claude.ai, Claude Code) silently exchange it for a new access token via `grant_type=refresh_token`, so you no longer re-run the consent flow every time the access token expires. Refresh tokens are stateless and signed: they keep working across server restarts and slide forward on each use, up to the `MCP_REFRESH_MAX_TTL_SECONDS` cap. Because they are stateless, this is sliding re-issuance, **not** reuse-detected rotation: the refresh token you just exchanged stays valid until its own expiry (it is not invalidated by the re-issue), and individual tokens **cannot be revoked** before then — rotate `MCP_TOKEN_SECRET` to invalidate all of them at once.
|
|
176
|
+
- DCR client registrations are **in-memory** — they don't survive a server restart. Refresh tokens do (the client binding is in the signature), so a restart no longer forces a re-login; only brand-new connections need DCR to re-run, which claude.ai does automatically.
|
|
173
177
|
|
|
174
178
|
### Connecting from headless agents (`client_credentials`)
|
|
175
179
|
|
|
@@ -187,6 +191,20 @@ curl -X POST https://YOUR_HOST/mcp \
|
|
|
187
191
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
188
192
|
```
|
|
189
193
|
|
|
194
|
+
The `authorization_code` grant additionally returns a `refresh_token`. To renew
|
|
195
|
+
an access token without re-running the consent flow:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
curl -X POST https://YOUR_HOST/mcp/oauth/token \
|
|
199
|
+
-d grant_type=refresh_token \
|
|
200
|
+
-d client_id=$CLIENT_ID \
|
|
201
|
+
-d refresh_token=$REFRESH_TOKEN
|
|
202
|
+
# -> { access_token, token_type, expires_in, refresh_token } (a new refresh_token is re-issued)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
(The headless `client_credentials` grant does **not** return a refresh token — it
|
|
206
|
+
just re-requests with the static secret.)
|
|
207
|
+
|
|
190
208
|
## Data model
|
|
191
209
|
|
|
192
210
|
- **`daily_metrics`**, **`weekly_metrics`** — `{ id (PK), isoDate / weekStartIsoDate, metricKey, value, createdAt, updatedAt }`. Composite unique on `(date, metricKey)`.
|
package/dist/app.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
+
import { activitiesRoutes } from './routes/activities.js';
|
|
3
|
+
import { activityHeartRateRoutes } from './routes/activity-heart-rate.js';
|
|
2
4
|
import { dailyMetricsRoutes } from './routes/daily-metrics.js';
|
|
3
5
|
import { dailyStressBurdenRoutes } from './routes/daily-stress-burden.js';
|
|
4
6
|
import { healthRoutes } from './routes/health.js';
|
|
@@ -10,6 +12,8 @@ import { userProfileRoutes } from './routes/user-profile.js';
|
|
|
10
12
|
import { userTimezonesRoutes } from './routes/user-timezones.js';
|
|
11
13
|
import { weeklyMetricsRoutes } from './routes/weekly-metrics.js';
|
|
12
14
|
import { wipeRoutes } from './routes/wipe.js';
|
|
15
|
+
import { createActivitiesStore } from './stores/activities.js';
|
|
16
|
+
import { createActivityHeartRateStore } from './stores/activity-heart-rate.js';
|
|
13
17
|
import { createDailyMetricsStore } from './stores/daily-metrics.js';
|
|
14
18
|
import { createDailyStressBurdenStore } from './stores/daily-stress-burden.js';
|
|
15
19
|
import { createHeartRateMinuteStore } from './stores/heart-rate-minute.js';
|
|
@@ -30,6 +34,8 @@ export function buildApp(db, env) {
|
|
|
30
34
|
const dailyStressBurdenStore = createDailyStressBurdenStore(db);
|
|
31
35
|
const heartRateMinuteStore = createHeartRateMinuteStore(db);
|
|
32
36
|
const hrZoneHistoryStore = createHrZoneHistoryStore(db);
|
|
37
|
+
const activitiesStore = createActivitiesStore(db);
|
|
38
|
+
const activityHeartRateStore = createActivityHeartRateStore(db);
|
|
33
39
|
app.route('/v1/sync/daily-metrics', dailyMetricsRoutes(dailyMetricsStore, auth));
|
|
34
40
|
app.route('/v1/sync/weekly-metrics', weeklyMetricsRoutes(weeklyMetricsStore, auth));
|
|
35
41
|
app.route('/v1/sync/sleep-sessions', sleepSessionsRoutes(sleepSessionsStore, auth));
|
|
@@ -38,6 +44,8 @@ export function buildApp(db, env) {
|
|
|
38
44
|
app.route('/v1/sync/daily-stress-burden', dailyStressBurdenRoutes(dailyStressBurdenStore, auth));
|
|
39
45
|
app.route('/v1/sync/heart-rate-minute', heartRateMinuteRoutes(heartRateMinuteStore, auth));
|
|
40
46
|
app.route('/v1/sync/hr-zone-history', hrZoneHistoryRoutes(hrZoneHistoryStore, auth));
|
|
47
|
+
app.route('/v1/sync/activities', activitiesRoutes(activitiesStore, auth));
|
|
48
|
+
app.route('/v1/sync/activity-heart-rate', activityHeartRateRoutes(activityHeartRateStore, auth));
|
|
41
49
|
app.route('/v1/sync/wipe', wipeRoutes({
|
|
42
50
|
dailyMetrics: dailyMetricsStore,
|
|
43
51
|
weeklyMetrics: weeklyMetricsStore,
|
|
@@ -47,6 +55,8 @@ export function buildApp(db, env) {
|
|
|
47
55
|
dailyStressBurden: dailyStressBurdenStore,
|
|
48
56
|
heartRateMinute: heartRateMinuteStore,
|
|
49
57
|
hrZoneHistory: hrZoneHistoryStore,
|
|
58
|
+
activities: activitiesStore,
|
|
59
|
+
activityHeartRate: activityHeartRateStore,
|
|
50
60
|
}, auth));
|
|
51
61
|
if (env.mcp) {
|
|
52
62
|
const { mcp, discovery } = mcpRoutes({ db, env, mcp: env.mcp });
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,MAAM,UAAU,QAAQ,CAAC,EAAM,EAAE,GAAQ;IACvC,MAAM,IAAI,GAAe,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;IACtF,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;IAE1C,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,EAAE,CAAC,CAAC;IACtD,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,EAAE,CAAC,CAAC;IACxD,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,EAAE,CAAC,CAAC;IACxD,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,EAAE,CAAC,CAAC;IACxD,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,EAAE,CAAC,CAAC;IACpD,MAAM,sBAAsB,GAAG,4BAA4B,CAAC,EAAE,CAAC,CAAC;IAChE,MAAM,oBAAoB,GAAG,0BAA0B,CAAC,EAAE,CAAC,CAAC;IAC5D,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|