@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 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 | 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 bearer tokens. ≥ 32 random bytes. Treat as a secret. |
120
- | `MCP_TOKEN_TTL_SECONDS` | `3600` | Bearer token lifetime. |
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 (both grants). |
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). Claude.ai re-runs the consent flow when expired.
172
- - DCR client registrations are **in-memory** they don't survive a server restart. If you restart the server, remove the connector in Claude.ai and re-add it; takes ten seconds.
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":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}