@zykeco/sync-server 0.4.0 → 0.6.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/dist/app.d.ts +4 -0
- package/dist/app.js +56 -0
- package/dist/app.js.map +1 -0
- package/dist/db/schema.d.ts +1009 -0
- package/dist/db/schema.js +80 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/env.d.ts +6 -0
- package/dist/env.js +9 -1
- package/dist/env.js.map +1 -1
- package/dist/index.js +2 -23
- package/dist/index.js.map +1 -1
- package/dist/mcp/metric-types.d.ts +2 -0
- package/dist/mcp/metric-types.js +42 -0
- package/dist/mcp/metric-types.js.map +1 -0
- package/dist/mcp/oauth.d.ts +41 -0
- package/dist/mcp/oauth.js +90 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp/server.d.ts +35 -0
- package/dist/mcp/server.js +94 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/timezones.d.ts +17 -0
- package/dist/mcp/timezones.js +32 -0
- package/dist/mcp/timezones.js.map +1 -0
- package/dist/mcp/tools.d.ts +4 -0
- package/dist/mcp/tools.js +236 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/transforms.d.ts +14 -0
- package/dist/mcp/transforms.js +45 -0
- package/dist/mcp/transforms.js.map +1 -0
- package/dist/routes/daily-stress-burden.d.ts +3 -0
- package/dist/routes/daily-stress-burden.js +24 -0
- package/dist/routes/daily-stress-burden.js.map +1 -0
- package/dist/routes/heart-rate-minute.d.ts +3 -0
- package/dist/routes/heart-rate-minute.js +24 -0
- package/dist/routes/heart-rate-minute.js.map +1 -0
- package/dist/routes/hr-zone-history.d.ts +3 -0
- package/dist/routes/hr-zone-history.js +24 -0
- package/dist/routes/hr-zone-history.js.map +1 -0
- package/dist/routes/mcp.d.ts +9 -0
- package/dist/routes/mcp.js +96 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/user-profile.d.ts +3 -0
- package/dist/routes/user-profile.js +24 -0
- package/dist/routes/user-profile.js.map +1 -0
- package/dist/routes/user-timezones.d.ts +3 -0
- package/dist/routes/user-timezones.js +24 -0
- package/dist/routes/user-timezones.js.map +1 -0
- package/dist/routes/wipe.d.ts +6 -1
- package/dist/routes/wipe.js +16 -2
- package/dist/routes/wipe.js.map +1 -1
- package/dist/stores/daily-stress-burden.d.ts +3 -0
- package/dist/stores/daily-stress-burden.js +58 -0
- package/dist/stores/daily-stress-burden.js.map +1 -0
- package/dist/stores/heart-rate-minute.d.ts +3 -0
- package/dist/stores/heart-rate-minute.js +48 -0
- package/dist/stores/heart-rate-minute.js.map +1 -0
- package/dist/stores/hr-zone-history.d.ts +3 -0
- package/dist/stores/hr-zone-history.js +49 -0
- package/dist/stores/hr-zone-history.js.map +1 -0
- package/dist/stores/user-profile.d.ts +3 -0
- package/dist/stores/user-profile.js +76 -0
- package/dist/stores/user-profile.js.map +1 -0
- package/dist/stores/user-timezones.d.ts +3 -0
- package/dist/stores/user-timezones.js +43 -0
- package/dist/stores/user-timezones.js.map +1 -0
- package/drizzle/0001_sweet_firebird.sql +78 -0
- package/drizzle/meta/0001_snapshot.json +733 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +4 -4
package/dist/db/schema.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { blob, check, index, integer, real, sqliteTable, text, uniqueIndex, } from 'drizzle-orm/sqlite-core';
|
|
2
3
|
export const blobs = sqliteTable('blobs', {
|
|
3
4
|
id: text('id').primaryKey(),
|
|
4
5
|
contentType: text('content_type').notNull(),
|
|
@@ -35,4 +36,82 @@ export const sleepSessions = sqliteTable('sleep_sessions', {
|
|
|
35
36
|
payloadVersion: integer('payload_version').notNull().default(1),
|
|
36
37
|
payload: text('payload', { mode: 'json' }).notNull(),
|
|
37
38
|
});
|
|
39
|
+
export const userTimezones = sqliteTable('user_timezones', {
|
|
40
|
+
id: integer('id').primaryKey({ autoIncrement: false }),
|
|
41
|
+
timezone: text('timezone').notNull(),
|
|
42
|
+
type: text('type').notNull(),
|
|
43
|
+
utcOffsetMinutes: integer('utc_offset_minutes').notNull(),
|
|
44
|
+
effectiveFrom: integer('effective_from').notNull(),
|
|
45
|
+
}, (t) => ({
|
|
46
|
+
effectiveFromIdx: index('user_tz_effective_from_idx').on(t.effectiveFrom),
|
|
47
|
+
}));
|
|
48
|
+
export const userProfile = sqliteTable('user_profile', {
|
|
49
|
+
id: integer('id').primaryKey({ autoIncrement: false }),
|
|
50
|
+
name: text('name').notNull(),
|
|
51
|
+
birthDate: text('birth_date').notNull(),
|
|
52
|
+
height: real('height').notNull(),
|
|
53
|
+
weight: real('weight').notNull(),
|
|
54
|
+
biologicalGender: text('biological_gender').notNull(),
|
|
55
|
+
maxHeartRate: integer('max_heart_rate'),
|
|
56
|
+
maxHeartRateManually: integer('max_heart_rate_manually'),
|
|
57
|
+
restingHeartRate: integer('resting_heart_rate'),
|
|
58
|
+
vo2Max: real('vo2_max'),
|
|
59
|
+
heartRateZonesManually: integer('heart_rate_zones_manually'),
|
|
60
|
+
hrZone1: integer('hr_zone_1'),
|
|
61
|
+
hrZone2: integer('hr_zone_2'),
|
|
62
|
+
hrZone3: integer('hr_zone_3'),
|
|
63
|
+
hrZone4: integer('hr_zone_4'),
|
|
64
|
+
hrZone5: integer('hr_zone_5'),
|
|
65
|
+
trainingBackground: text('training_background'),
|
|
66
|
+
typicalDay: text('typical_day'),
|
|
67
|
+
sleepGoalMinutes: integer('sleep_goal_minutes'),
|
|
68
|
+
dataRetentionLevel: text('data_retention_level'),
|
|
69
|
+
createdAt: integer('created_at').notNull(),
|
|
70
|
+
updatedAt: integer('updated_at').notNull(),
|
|
71
|
+
}, (t) => ({
|
|
72
|
+
singleton: check('user_profile_singleton', sql `${t.id} = 1`),
|
|
73
|
+
}));
|
|
74
|
+
export const dailyStressBurden = sqliteTable('daily_stress_burden', {
|
|
75
|
+
id: integer('id').primaryKey({ autoIncrement: false }),
|
|
76
|
+
isoDate: text('iso_date').notNull(),
|
|
77
|
+
timezone: text('timezone').notNull(),
|
|
78
|
+
intervalsRelaxed: integer('intervals_relaxed').notNull(),
|
|
79
|
+
intervalsBalanced: integer('intervals_balanced').notNull(),
|
|
80
|
+
intervalsElevated: integer('intervals_elevated').notNull(),
|
|
81
|
+
intervalsHigh: integer('intervals_high').notNull(),
|
|
82
|
+
intervalsScored: integer('intervals_scored').notNull(),
|
|
83
|
+
meanScore: real('mean_score').notNull(),
|
|
84
|
+
peakScore: real('peak_score').notNull(),
|
|
85
|
+
peakScoreUtc: integer('peak_score_utc').notNull(),
|
|
86
|
+
stressBurden: real('stress_burden').notNull(),
|
|
87
|
+
updatedAt: integer('updated_at').notNull(),
|
|
88
|
+
}, (t) => ({
|
|
89
|
+
isoDateIdx: uniqueIndex('dsb_unique_date_idx').on(t.isoDate),
|
|
90
|
+
}));
|
|
91
|
+
export const heartRateMinute = sqliteTable('heart_rate_minute', {
|
|
92
|
+
id: integer('id').primaryKey({ autoIncrement: false }),
|
|
93
|
+
minuteStartUtc: integer('minute_start_utc').notNull(),
|
|
94
|
+
bpmMean: real('bpm_mean').notNull(),
|
|
95
|
+
bpmMin: integer('bpm_min').notNull(),
|
|
96
|
+
bpmMax: integer('bpm_max').notNull(),
|
|
97
|
+
sampleCount: integer('sample_count').notNull(),
|
|
98
|
+
deviceId: integer('device_id').notNull(),
|
|
99
|
+
updatedAt: integer('updated_at').notNull(),
|
|
100
|
+
}, (t) => ({
|
|
101
|
+
minuteIdx: index('hrm_minute_idx').on(t.minuteStartUtc),
|
|
102
|
+
uniqueMinutePerDevice: uniqueIndex('hrm_unique_minute_per_device_idx').on(t.minuteStartUtc, t.deviceId),
|
|
103
|
+
deviceUpdatedIdx: index('hrm_device_updated_idx').on(t.deviceId, t.updatedAt),
|
|
104
|
+
}));
|
|
105
|
+
export const hrZoneHistory = sqliteTable('hr_zone_history', {
|
|
106
|
+
id: integer('id').primaryKey({ autoIncrement: false }),
|
|
107
|
+
effectiveFromIsoDate: text('effective_from_iso_date').notNull(),
|
|
108
|
+
zone1: integer('zone1').notNull(),
|
|
109
|
+
zone2: integer('zone2').notNull(),
|
|
110
|
+
zone3: integer('zone3').notNull(),
|
|
111
|
+
zone4: integer('zone4').notNull(),
|
|
112
|
+
zone5: integer('zone5').notNull(),
|
|
113
|
+
createdAt: integer('created_at').notNull(),
|
|
114
|
+
}, (t) => ({
|
|
115
|
+
uniqueEffective: uniqueIndex('hrzh_unique_effective_idx').on(t.effectiveFromIsoDate),
|
|
116
|
+
}));
|
|
38
117
|
//# sourceMappingURL=schema.js.map
|
package/dist/db/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/env.d.ts
CHANGED
|
@@ -4,5 +4,11 @@ export interface Env {
|
|
|
4
4
|
databaseUrl: string;
|
|
5
5
|
readSecret: string;
|
|
6
6
|
writeSecret: string;
|
|
7
|
+
mcp: McpEnv | null;
|
|
8
|
+
}
|
|
9
|
+
export interface McpEnv {
|
|
10
|
+
clientId: string;
|
|
11
|
+
tokenSecret: string;
|
|
12
|
+
tokenTtlSeconds: number;
|
|
7
13
|
}
|
|
8
14
|
export declare function loadEnv(): Env;
|
package/dist/env.js
CHANGED
|
@@ -4,7 +4,15 @@ export function loadEnv() {
|
|
|
4
4
|
const databaseUrl = process.env.DATABASE_URL ?? 'file:./data/sync.db';
|
|
5
5
|
const readSecret = required('READ_SECRET');
|
|
6
6
|
const writeSecret = required('WRITE_SECRET');
|
|
7
|
-
|
|
7
|
+
const mcpEnabled = (process.env.MCP_ENABLED ?? 'true').toLowerCase() !== 'false';
|
|
8
|
+
const mcp = mcpEnabled
|
|
9
|
+
? {
|
|
10
|
+
clientId: required('MCP_CLIENT_ID'),
|
|
11
|
+
tokenSecret: required('MCP_TOKEN_SECRET'),
|
|
12
|
+
tokenTtlSeconds: Number(process.env.MCP_TOKEN_TTL_SECONDS ?? 3600),
|
|
13
|
+
}
|
|
14
|
+
: null;
|
|
15
|
+
return { port, databaseUrl, readSecret, writeSecret, mcp };
|
|
8
16
|
}
|
|
9
17
|
function required(name) {
|
|
10
18
|
const value = process.env[name];
|
package/dist/env.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAgBvB,MAAM,UAAU,OAAO;IACrB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,qBAAqB,CAAC;IACtE,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;IAE7C,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC;IACjF,MAAM,GAAG,GAAG,UAAU;QACpB,CAAC,CAAC;YACE,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC;YACnC,WAAW,EAAE,QAAQ,CAAC,kBAAkB,CAAC;YACzC,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,IAAI,CAAC;SACnE;QACH,CAAC,CAAC,IAAI,CAAC;IAET,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;AAC7D,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
import { serve } from '@hono/node-server';
|
|
2
|
-
import {
|
|
2
|
+
import { buildApp } from './app.js';
|
|
3
3
|
import { createDb } from './db/client.js';
|
|
4
4
|
import { runMigrations } from './db/migrate.js';
|
|
5
5
|
import { loadEnv } from './env.js';
|
|
6
|
-
import { dailyMetricsRoutes } from './routes/daily-metrics.js';
|
|
7
|
-
import { healthRoutes } from './routes/health.js';
|
|
8
|
-
import { sleepSessionsRoutes } from './routes/sleep-sessions.js';
|
|
9
|
-
import { weeklyMetricsRoutes } from './routes/weekly-metrics.js';
|
|
10
|
-
import { wipeRoutes } from './routes/wipe.js';
|
|
11
|
-
import { createDailyMetricsStore } from './stores/daily-metrics.js';
|
|
12
|
-
import { createSleepSessionsStore } from './stores/sleep-sessions.js';
|
|
13
|
-
import { createWeeklyMetricsStore } from './stores/weekly-metrics.js';
|
|
14
6
|
const env = loadEnv();
|
|
15
7
|
const db = createDb(env.databaseUrl);
|
|
16
8
|
await runMigrations(db);
|
|
17
|
-
const
|
|
18
|
-
const app = new Hono();
|
|
19
|
-
app.route('/v1/health', healthRoutes(db));
|
|
20
|
-
const dailyMetricsStore = createDailyMetricsStore(db);
|
|
21
|
-
const weeklyMetricsStore = createWeeklyMetricsStore(db);
|
|
22
|
-
const sleepSessionsStore = createSleepSessionsStore(db);
|
|
23
|
-
app.route('/v1/sync/daily-metrics', dailyMetricsRoutes(dailyMetricsStore, auth));
|
|
24
|
-
app.route('/v1/sync/weekly-metrics', weeklyMetricsRoutes(weeklyMetricsStore, auth));
|
|
25
|
-
app.route('/v1/sync/sleep-sessions', sleepSessionsRoutes(sleepSessionsStore, auth));
|
|
26
|
-
app.route('/v1/sync/wipe', wipeRoutes({
|
|
27
|
-
dailyMetrics: dailyMetricsStore,
|
|
28
|
-
weeklyMetrics: weeklyMetricsStore,
|
|
29
|
-
sleepSessions: sleepSessionsStore,
|
|
30
|
-
}, auth));
|
|
9
|
+
const app = buildApp(db, env);
|
|
31
10
|
serve({ fetch: app.fetch, port: env.port }, (info) => {
|
|
32
11
|
console.info(`zyke-sync listening on http://localhost:${info.port}`);
|
|
33
12
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AACtB,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAErC,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;AAExB,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;AAE9B,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE;IACnD,OAAO,CAAC,IAAI,CAAC,2CAA2C,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-curated overrides for metric keys whose name doesn't follow the suffix
|
|
3
|
+
* convention. Add entries here as we learn about ambiguous keys. The suffix
|
|
4
|
+
* inference below handles the common case.
|
|
5
|
+
*/
|
|
6
|
+
const OVERRIDES = {
|
|
7
|
+
steps: 'count',
|
|
8
|
+
active_calories: 'kcal',
|
|
9
|
+
total_calories: 'kcal',
|
|
10
|
+
vo2_max: 'score',
|
|
11
|
+
weight: 'kilograms',
|
|
12
|
+
height: 'meters',
|
|
13
|
+
sleep_efficiency: 'ratio',
|
|
14
|
+
};
|
|
15
|
+
// Order matters — first match wins, so place longer/more specific suffixes first.
|
|
16
|
+
const SUFFIX_RULES = [
|
|
17
|
+
{ suffix: '_ms', type: 'milliseconds' },
|
|
18
|
+
{ suffix: '_minutes', type: 'minutes' },
|
|
19
|
+
{ suffix: '_min', type: 'minutes' },
|
|
20
|
+
{ suffix: '_s', type: 'seconds' },
|
|
21
|
+
{ suffix: '_bpm', type: 'bpm' },
|
|
22
|
+
{ suffix: '_percent', type: 'percent' },
|
|
23
|
+
{ suffix: '_pct', type: 'percent' },
|
|
24
|
+
{ suffix: '_ratio', type: 'ratio' },
|
|
25
|
+
{ suffix: '_score', type: 'score' },
|
|
26
|
+
{ suffix: '_count', type: 'count' },
|
|
27
|
+
{ suffix: '_kcal', type: 'kcal' },
|
|
28
|
+
{ suffix: '_kg', type: 'kilograms' },
|
|
29
|
+
{ suffix: '_m', type: 'meters' },
|
|
30
|
+
{ suffix: '_c', type: 'celsius' },
|
|
31
|
+
];
|
|
32
|
+
export function inferValueType(metricKey) {
|
|
33
|
+
const override = OVERRIDES[metricKey];
|
|
34
|
+
if (override)
|
|
35
|
+
return override;
|
|
36
|
+
for (const rule of SUFFIX_RULES) {
|
|
37
|
+
if (metricKey.endsWith(rule.suffix))
|
|
38
|
+
return rule.type;
|
|
39
|
+
}
|
|
40
|
+
return 'unknown';
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=metric-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-types.js","sourceRoot":"","sources":["../../src/mcp/metric-types.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,MAAM,SAAS,GAAoC;IACjD,KAAK,EAAE,OAAO;IACd,eAAe,EAAE,MAAM;IACvB,cAAc,EAAE,MAAM;IACtB,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,QAAQ;IAChB,gBAAgB,EAAE,OAAO;CAC1B,CAAC;AAOF,kFAAkF;AAClF,MAAM,YAAY,GAAiB;IACjC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE;IACvC,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE;IACvC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;IACnC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;IACjC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE;IAC/B,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE;IACvC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;IACnC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;IACnC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;IACnC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;IACnC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE;IACjC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE;IACpC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAChC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;CAClC,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IACxD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { McpEnv } from '../env.js';
|
|
2
|
+
export interface TokenIssueResult {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
expiresIn: number;
|
|
5
|
+
}
|
|
6
|
+
export type TokenVerifyResult = {
|
|
7
|
+
ok: true;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
reason: 'malformed' | 'bad_signature' | 'expired';
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Issue an opaque, HMAC-signed bearer token.
|
|
15
|
+
*
|
|
16
|
+
* Token layout: `${b64url(payload)}.${hex(hmac_sha256(payload, secret))}`
|
|
17
|
+
* where `payload = random(16) || expiryUnixSeconds(uint64BE)`.
|
|
18
|
+
*
|
|
19
|
+
* Stateless — verification needs only the signing secret.
|
|
20
|
+
*/
|
|
21
|
+
export declare function issueToken(env: McpEnv, nowMs?: number): TokenIssueResult;
|
|
22
|
+
export declare function verifyToken(token: string, env: McpEnv, nowMs?: number): TokenVerifyResult;
|
|
23
|
+
export interface ClientCredentialsRequest {
|
|
24
|
+
clientId: string;
|
|
25
|
+
clientSecret: string;
|
|
26
|
+
grantType: string;
|
|
27
|
+
}
|
|
28
|
+
export type GrantResult = {
|
|
29
|
+
ok: true;
|
|
30
|
+
token: TokenIssueResult;
|
|
31
|
+
} | {
|
|
32
|
+
ok: false;
|
|
33
|
+
status: number;
|
|
34
|
+
error: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Validate a client_credentials grant request against the configured MCP env
|
|
39
|
+
* + the read secret. Returns the access token on success.
|
|
40
|
+
*/
|
|
41
|
+
export declare function grantClientCredentials(req: ClientCredentialsRequest, env: McpEnv, readSecret: string, nowMs?: number): GrantResult;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
const PAYLOAD_RANDOM_BYTES = 16;
|
|
3
|
+
const PAYLOAD_EXPIRY_BYTES = 8;
|
|
4
|
+
const PAYLOAD_BYTES = PAYLOAD_RANDOM_BYTES + PAYLOAD_EXPIRY_BYTES;
|
|
5
|
+
const SIG_HEX_LEN = 64; // sha256 hex = 64 chars
|
|
6
|
+
function sign(payload, secret) {
|
|
7
|
+
return createHmac('sha256', secret).update(payload).digest('hex');
|
|
8
|
+
}
|
|
9
|
+
function b64urlEncode(buf) {
|
|
10
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
11
|
+
}
|
|
12
|
+
function b64urlDecode(s) {
|
|
13
|
+
try {
|
|
14
|
+
const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));
|
|
15
|
+
return Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/') + pad, 'base64');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Issue an opaque, HMAC-signed bearer token.
|
|
23
|
+
*
|
|
24
|
+
* Token layout: `${b64url(payload)}.${hex(hmac_sha256(payload, secret))}`
|
|
25
|
+
* where `payload = random(16) || expiryUnixSeconds(uint64BE)`.
|
|
26
|
+
*
|
|
27
|
+
* Stateless — verification needs only the signing secret.
|
|
28
|
+
*/
|
|
29
|
+
export function issueToken(env, nowMs = Date.now()) {
|
|
30
|
+
const expiresAt = Math.floor(nowMs / 1000) + env.tokenTtlSeconds;
|
|
31
|
+
const payload = Buffer.alloc(PAYLOAD_BYTES);
|
|
32
|
+
randomBytes(PAYLOAD_RANDOM_BYTES).copy(payload, 0);
|
|
33
|
+
payload.writeBigUInt64BE(BigInt(expiresAt), PAYLOAD_RANDOM_BYTES);
|
|
34
|
+
const sig = sign(payload, env.tokenSecret);
|
|
35
|
+
return {
|
|
36
|
+
accessToken: `${b64urlEncode(payload)}.${sig}`,
|
|
37
|
+
expiresIn: env.tokenTtlSeconds,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function verifyToken(token, env, nowMs = Date.now()) {
|
|
41
|
+
const dot = token.indexOf('.');
|
|
42
|
+
if (dot <= 0)
|
|
43
|
+
return { ok: false, reason: 'malformed' };
|
|
44
|
+
const payloadB64 = token.slice(0, dot);
|
|
45
|
+
const sigHex = token.slice(dot + 1);
|
|
46
|
+
if (sigHex.length !== SIG_HEX_LEN)
|
|
47
|
+
return { ok: false, reason: 'malformed' };
|
|
48
|
+
const payload = b64urlDecode(payloadB64);
|
|
49
|
+
if (!payload || payload.length !== PAYLOAD_BYTES)
|
|
50
|
+
return { ok: false, reason: 'malformed' };
|
|
51
|
+
const expectedSigHex = sign(payload, env.tokenSecret);
|
|
52
|
+
const a = Buffer.from(sigHex, 'hex');
|
|
53
|
+
const b = Buffer.from(expectedSigHex, 'hex');
|
|
54
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
55
|
+
return { ok: false, reason: 'bad_signature' };
|
|
56
|
+
}
|
|
57
|
+
const expiresAt = Number(payload.readBigUInt64BE(PAYLOAD_RANDOM_BYTES));
|
|
58
|
+
if (Math.floor(nowMs / 1000) >= expiresAt) {
|
|
59
|
+
return { ok: false, reason: 'expired' };
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, expiresAt };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validate a client_credentials grant request against the configured MCP env
|
|
65
|
+
* + the read secret. Returns the access token on success.
|
|
66
|
+
*/
|
|
67
|
+
export function grantClientCredentials(req, env, readSecret, nowMs = Date.now()) {
|
|
68
|
+
if (req.grantType !== 'client_credentials') {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
status: 400,
|
|
72
|
+
error: 'unsupported_grant_type',
|
|
73
|
+
description: 'only client_credentials is supported',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (!req.clientId || !req.clientSecret) {
|
|
77
|
+
return { ok: false, status: 400, error: 'invalid_request', description: 'missing credentials' };
|
|
78
|
+
}
|
|
79
|
+
const cidA = Buffer.from(req.clientId);
|
|
80
|
+
const cidB = Buffer.from(env.clientId);
|
|
81
|
+
const secA = Buffer.from(req.clientSecret);
|
|
82
|
+
const secB = Buffer.from(readSecret);
|
|
83
|
+
const cidOk = cidA.length === cidB.length && timingSafeEqual(cidA, cidB);
|
|
84
|
+
const secOk = secA.length === secB.length && timingSafeEqual(secA, secB);
|
|
85
|
+
if (!cidOk || !secOk) {
|
|
86
|
+
return { ok: false, status: 401, error: 'invalid_client' };
|
|
87
|
+
}
|
|
88
|
+
return { ok: true, token: issueToken(env, nowMs) };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/mcp/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TSchema } from '@sinclair/typebox';
|
|
2
|
+
export interface ToolDefinition {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: TSchema;
|
|
6
|
+
handler: (args: any) => Promise<unknown> | unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface JsonRpcRequest {
|
|
9
|
+
jsonrpc: '2.0';
|
|
10
|
+
id?: string | number | null;
|
|
11
|
+
method: string;
|
|
12
|
+
params?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface JsonRpcSuccess {
|
|
15
|
+
jsonrpc: '2.0';
|
|
16
|
+
id: string | number | null;
|
|
17
|
+
result: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface JsonRpcError {
|
|
20
|
+
jsonrpc: '2.0';
|
|
21
|
+
id: string | number | null;
|
|
22
|
+
error: {
|
|
23
|
+
code: number;
|
|
24
|
+
message: string;
|
|
25
|
+
data?: unknown;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export type JsonRpcResponse = JsonRpcSuccess | JsonRpcError;
|
|
29
|
+
export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
30
|
+
export declare const MCP_SERVER_NAME = "zyke-sync";
|
|
31
|
+
export interface McpServer {
|
|
32
|
+
handle(req: unknown): Promise<JsonRpcResponse>;
|
|
33
|
+
listToolNames(): string[];
|
|
34
|
+
}
|
|
35
|
+
export declare function createMcpServer(tools: ToolDefinition[]): McpServer;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Value } from '@sinclair/typebox/value';
|
|
2
|
+
export const MCP_PROTOCOL_VERSION = '2025-06-18';
|
|
3
|
+
export const MCP_SERVER_NAME = 'zyke-sync';
|
|
4
|
+
const ERR_PARSE = -32700;
|
|
5
|
+
const ERR_INVALID_REQUEST = -32600;
|
|
6
|
+
const ERR_METHOD_NOT_FOUND = -32601;
|
|
7
|
+
const ERR_INVALID_PARAMS = -32602;
|
|
8
|
+
const ERR_INTERNAL = -32603;
|
|
9
|
+
function errorResponse(id, code, message, data) {
|
|
10
|
+
const err = { code, message };
|
|
11
|
+
if (data !== undefined)
|
|
12
|
+
err.data = data;
|
|
13
|
+
return { jsonrpc: '2.0', id, error: err };
|
|
14
|
+
}
|
|
15
|
+
function successResponse(id, result) {
|
|
16
|
+
return { jsonrpc: '2.0', id, result };
|
|
17
|
+
}
|
|
18
|
+
export function createMcpServer(tools) {
|
|
19
|
+
const toolMap = new Map(tools.map((t) => [t.name, t]));
|
|
20
|
+
async function dispatch(req) {
|
|
21
|
+
const id = req.id ?? null;
|
|
22
|
+
switch (req.method) {
|
|
23
|
+
case 'initialize':
|
|
24
|
+
return successResponse(id, {
|
|
25
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
26
|
+
capabilities: { tools: { listChanged: false } },
|
|
27
|
+
serverInfo: { name: MCP_SERVER_NAME, version: '1.0.0' },
|
|
28
|
+
});
|
|
29
|
+
case 'notifications/initialized':
|
|
30
|
+
case 'ping':
|
|
31
|
+
return successResponse(id, {});
|
|
32
|
+
case 'tools/list':
|
|
33
|
+
return successResponse(id, {
|
|
34
|
+
tools: tools.map((t) => ({
|
|
35
|
+
name: t.name,
|
|
36
|
+
description: t.description,
|
|
37
|
+
inputSchema: t.inputSchema,
|
|
38
|
+
})),
|
|
39
|
+
});
|
|
40
|
+
case 'tools/call': {
|
|
41
|
+
const params = req.params;
|
|
42
|
+
if (!params || typeof params.name !== 'string') {
|
|
43
|
+
return errorResponse(id, ERR_INVALID_PARAMS, 'missing tool name');
|
|
44
|
+
}
|
|
45
|
+
const tool = toolMap.get(params.name);
|
|
46
|
+
if (!tool)
|
|
47
|
+
return errorResponse(id, ERR_METHOD_NOT_FOUND, `unknown tool: ${params.name}`);
|
|
48
|
+
const args = params.arguments ?? {};
|
|
49
|
+
if (!Value.Check(tool.inputSchema, args)) {
|
|
50
|
+
const first = [...Value.Errors(tool.inputSchema, args)][0];
|
|
51
|
+
const detail = first ? `${first.path} ${first.message}`.trim() : 'invalid arguments';
|
|
52
|
+
return errorResponse(id, ERR_INVALID_PARAMS, detail);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const result = await tool.handler(args);
|
|
56
|
+
return successResponse(id, {
|
|
57
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
58
|
+
structuredContent: result,
|
|
59
|
+
isError: false,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
return successResponse(id, {
|
|
65
|
+
content: [{ type: 'text', text: `tool error: ${message}` }],
|
|
66
|
+
isError: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
default:
|
|
71
|
+
return errorResponse(id, ERR_METHOD_NOT_FOUND, `unknown method: ${req.method}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
listToolNames: () => tools.map((t) => t.name),
|
|
76
|
+
async handle(raw) {
|
|
77
|
+
if (!raw || typeof raw !== 'object') {
|
|
78
|
+
return errorResponse(null, ERR_PARSE, 'request must be a JSON object');
|
|
79
|
+
}
|
|
80
|
+
const req = raw;
|
|
81
|
+
if (req.jsonrpc !== '2.0' || typeof req.method !== 'string') {
|
|
82
|
+
return errorResponse(req.id ?? null, ERR_INVALID_REQUEST, 'malformed JSON-RPC request');
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return await dispatch(req);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
89
|
+
return errorResponse(req.id ?? null, ERR_INTERNAL, message);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Db } from '../db/client.js';
|
|
2
|
+
export interface TimezoneRow {
|
|
3
|
+
timezone: string;
|
|
4
|
+
utcOffsetMinutes: number;
|
|
5
|
+
effectiveFrom: number;
|
|
6
|
+
}
|
|
7
|
+
export interface TimezoneResolver {
|
|
8
|
+
/** Returns the timezone active at the given UTC ms, or null if none. */
|
|
9
|
+
resolveAt(utcMs: number): TimezoneRow | null;
|
|
10
|
+
/** Force-reload from DB. Called between requests if needed. */
|
|
11
|
+
reload(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Cache the (small) user_timezones table in memory, sorted by effectiveFrom
|
|
15
|
+
* descending. Lookups are O(log n) via binary search on the sorted list.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createTimezoneResolver(db: Db): TimezoneResolver;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { desc } from 'drizzle-orm';
|
|
2
|
+
import { userTimezones } from '../db/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Cache the (small) user_timezones table in memory, sorted by effectiveFrom
|
|
5
|
+
* descending. Lookups are O(log n) via binary search on the sorted list.
|
|
6
|
+
*/
|
|
7
|
+
export function createTimezoneResolver(db) {
|
|
8
|
+
let cache = [];
|
|
9
|
+
async function reload() {
|
|
10
|
+
const rows = await db
|
|
11
|
+
.select({
|
|
12
|
+
timezone: userTimezones.timezone,
|
|
13
|
+
utcOffsetMinutes: userTimezones.utcOffsetMinutes,
|
|
14
|
+
effectiveFrom: userTimezones.effectiveFrom,
|
|
15
|
+
})
|
|
16
|
+
.from(userTimezones)
|
|
17
|
+
.orderBy(desc(userTimezones.effectiveFrom));
|
|
18
|
+
cache = rows;
|
|
19
|
+
}
|
|
20
|
+
function resolveAt(utcMs) {
|
|
21
|
+
if (!Number.isFinite(utcMs))
|
|
22
|
+
return null;
|
|
23
|
+
// cache is sorted descending by effectiveFrom; first row whose effectiveFrom <= utcMs wins.
|
|
24
|
+
for (const row of cache) {
|
|
25
|
+
if (row.effectiveFrom <= utcMs)
|
|
26
|
+
return row;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return { resolveAt, reload };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=timezones.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timezones.js","sourceRoot":"","sources":["../../src/mcp/timezones.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGnC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAehD;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAM;IAC3C,IAAI,KAAK,GAAkB,EAAE,CAAC;IAE9B,KAAK,UAAU,MAAM;QACnB,MAAM,IAAI,GAAG,MAAM,EAAE;aAClB,MAAM,CAAC;YACN,QAAQ,EAAE,aAAa,CAAC,QAAQ;YAChC,gBAAgB,EAAE,aAAa,CAAC,gBAAgB;YAChD,aAAa,EAAE,aAAa,CAAC,aAAa;SAC3C,CAAC;aACD,IAAI,CAAC,aAAa,CAAC;aACnB,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;QAC9C,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;IAED,SAAS,SAAS,CAAC,KAAa;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,4FAA4F;QAC5F,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;YACxB,IAAI,GAAG,CAAC,aAAa,IAAI,KAAK;gBAAE,OAAO,GAAG,CAAC;QAC7C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC"}
|