@zykeco/sync-server 0.5.0 → 0.7.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 +70 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +58 -0
- package/dist/app.js.map +1 -0
- 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 -48
- package/dist/index.js.map +1 -1
- package/dist/mcp/auth-store.d.ts +25 -0
- package/dist/mcp/auth-store.js +51 -0
- package/dist/mcp/auth-store.js.map +1 -0
- package/dist/mcp/consent.d.ts +16 -0
- package/dist/mcp/consent.js +90 -0
- package/dist/mcp/consent.js.map +1 -0
- 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 +61 -0
- package/dist/mcp/oauth.js +149 -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/mcp.d.ts +20 -0
- package/dist/routes/mcp.js +315 -0
- package/dist/routes/mcp.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createHash, 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
|
+
/**
|
|
91
|
+
* Compute the PKCE S256 code challenge for a verifier.
|
|
92
|
+
*
|
|
93
|
+
* RFC 7636 §4.2: `BASE64URL(SHA256(ASCII(verifier)))`.
|
|
94
|
+
*/
|
|
95
|
+
export function pkceS256Challenge(verifier) {
|
|
96
|
+
return b64urlEncode(createHash('sha256').update(verifier).digest());
|
|
97
|
+
}
|
|
98
|
+
function timingSafeEqualStr(a, b) {
|
|
99
|
+
if (a.length !== b.length)
|
|
100
|
+
return false;
|
|
101
|
+
return timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Validate an authorization_code grant + PKCE verifier and exchange it for a
|
|
105
|
+
* bearer token. The code is single-use — `consumeCode` deletes it regardless
|
|
106
|
+
* of validation outcome past the lookup.
|
|
107
|
+
*/
|
|
108
|
+
export function grantAuthorizationCode(req, env, store, nowMs = Date.now()) {
|
|
109
|
+
if (req.grantType !== 'authorization_code') {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
status: 400,
|
|
113
|
+
error: 'unsupported_grant_type',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (!req.code || !req.clientId || !req.codeVerifier || !req.redirectUri) {
|
|
117
|
+
return { ok: false, status: 400, error: 'invalid_request', description: 'missing parameter' };
|
|
118
|
+
}
|
|
119
|
+
// RFC 7636: verifier is 43-128 chars of [A-Z a-z 0-9 - . _ ~]
|
|
120
|
+
if (!/^[A-Za-z0-9._~-]{43,128}$/.test(req.codeVerifier)) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
status: 400,
|
|
124
|
+
error: 'invalid_grant',
|
|
125
|
+
description: 'malformed code_verifier',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const rec = store.consumeCode(req.code, nowMs);
|
|
129
|
+
if (!rec) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
status: 400,
|
|
133
|
+
error: 'invalid_grant',
|
|
134
|
+
description: 'code unknown or expired',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (rec.clientId !== req.clientId) {
|
|
138
|
+
return { ok: false, status: 400, error: 'invalid_grant', description: 'client_id mismatch' };
|
|
139
|
+
}
|
|
140
|
+
if (rec.redirectUri !== req.redirectUri) {
|
|
141
|
+
return { ok: false, status: 400, error: 'invalid_grant', description: 'redirect_uri mismatch' };
|
|
142
|
+
}
|
|
143
|
+
const expected = pkceS256Challenge(req.codeVerifier);
|
|
144
|
+
if (!timingSafeEqualStr(expected, rec.codeChallenge)) {
|
|
145
|
+
return { ok: false, status: 400, error: 'invalid_grant', description: 'pkce mismatch' };
|
|
146
|
+
}
|
|
147
|
+
return { ok: true, token: issueToken(env, nowMs) };
|
|
148
|
+
}
|
|
149
|
+
//# 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"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { and, asc, desc, eq, gt, gte, lte } from 'drizzle-orm';
|
|
3
|
+
import { dailyMetrics, dailyStressBurden, heartRateMinute, hrZoneHistory, sleepSessions, userProfile, userTimezones, weeklyMetrics, } from '../db/schema.js';
|
|
4
|
+
import { inferValueType } from './metric-types.js';
|
|
5
|
+
import { formatHmsFromSeconds, formatTzIso } from './transforms.js';
|
|
6
|
+
const Limit = Type.Optional(Type.Integer({ minimum: 1, maximum: 500, default: 100 }));
|
|
7
|
+
const IsoDate = Type.String({ pattern: '^\\d{4}-\\d{2}-\\d{2}$' });
|
|
8
|
+
const UnixMs = Type.Integer({ minimum: 0 });
|
|
9
|
+
function tzIso(resolver, utcMs) {
|
|
10
|
+
const row = resolver.resolveAt(utcMs);
|
|
11
|
+
if (!row)
|
|
12
|
+
return null;
|
|
13
|
+
return formatTzIso(utcMs, row.utcOffsetMinutes);
|
|
14
|
+
}
|
|
15
|
+
function applyLimit(n) {
|
|
16
|
+
if (n === undefined)
|
|
17
|
+
return 100;
|
|
18
|
+
return Math.min(Math.max(1, n), 500);
|
|
19
|
+
}
|
|
20
|
+
function and$(...parts) {
|
|
21
|
+
const filtered = parts.filter((p) => p !== undefined);
|
|
22
|
+
if (filtered.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (filtered.length === 1)
|
|
25
|
+
return filtered[0];
|
|
26
|
+
return and(...filtered);
|
|
27
|
+
}
|
|
28
|
+
export function buildTools(db, tz) {
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
name: 'list_user_timezones',
|
|
32
|
+
description: 'List user timezone history entries (initial / travel / DST). Each row records the timezone that became active at `effectiveFrom` (UTC ms).',
|
|
33
|
+
inputSchema: Type.Object({
|
|
34
|
+
since: Type.Optional(UnixMs),
|
|
35
|
+
until: Type.Optional(UnixMs),
|
|
36
|
+
limit: Limit,
|
|
37
|
+
}, { additionalProperties: false }),
|
|
38
|
+
async handler(args) {
|
|
39
|
+
await tz.reload();
|
|
40
|
+
const where = and$(args.since !== undefined ? gte(userTimezones.effectiveFrom, args.since) : undefined, args.until !== undefined ? lte(userTimezones.effectiveFrom, args.until) : undefined);
|
|
41
|
+
const q = db.select().from(userTimezones).limit(applyLimit(args.limit));
|
|
42
|
+
const rows = where
|
|
43
|
+
? await q.where(where).orderBy(desc(userTimezones.effectiveFrom))
|
|
44
|
+
: await q.orderBy(desc(userTimezones.effectiveFrom));
|
|
45
|
+
return { rows };
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'get_user_profile',
|
|
50
|
+
description: 'Return the singleton user profile row, or null if none has been synced.',
|
|
51
|
+
inputSchema: Type.Object({}, { additionalProperties: false }),
|
|
52
|
+
async handler() {
|
|
53
|
+
const rows = await db.select().from(userProfile).where(eq(userProfile.id, 1)).limit(1);
|
|
54
|
+
return { profile: rows[0] ?? null };
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'list_daily_metrics',
|
|
59
|
+
description: 'List daily metrics with a `valueType` annotation inferred from the metric key (e.g. sleep_optimum_s → seconds).',
|
|
60
|
+
inputSchema: Type.Object({
|
|
61
|
+
isoDateFrom: Type.Optional(IsoDate),
|
|
62
|
+
isoDateTo: Type.Optional(IsoDate),
|
|
63
|
+
metricKey: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),
|
|
64
|
+
limit: Limit,
|
|
65
|
+
}, { additionalProperties: false }),
|
|
66
|
+
async handler(args) {
|
|
67
|
+
const where = and$(args.isoDateFrom ? gte(dailyMetrics.isoDate, args.isoDateFrom) : undefined, args.isoDateTo ? lte(dailyMetrics.isoDate, args.isoDateTo) : undefined, args.metricKey ? eq(dailyMetrics.metricKey, args.metricKey) : undefined);
|
|
68
|
+
const q = db
|
|
69
|
+
.select()
|
|
70
|
+
.from(dailyMetrics)
|
|
71
|
+
.orderBy(desc(dailyMetrics.isoDate), asc(dailyMetrics.metricKey))
|
|
72
|
+
.limit(applyLimit(args.limit));
|
|
73
|
+
const rows = where ? await q.where(where) : await q;
|
|
74
|
+
return {
|
|
75
|
+
rows: rows.map((r) => ({ ...r, valueType: inferValueType(r.metricKey) })),
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'list_weekly_metrics',
|
|
81
|
+
description: 'List weekly metrics with `valueType` annotations.',
|
|
82
|
+
inputSchema: Type.Object({
|
|
83
|
+
weekStartFrom: Type.Optional(IsoDate),
|
|
84
|
+
weekStartTo: Type.Optional(IsoDate),
|
|
85
|
+
metricKey: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),
|
|
86
|
+
limit: Limit,
|
|
87
|
+
}, { additionalProperties: false }),
|
|
88
|
+
async handler(args) {
|
|
89
|
+
const where = and$(args.weekStartFrom ? gte(weeklyMetrics.weekStartIsoDate, args.weekStartFrom) : undefined, args.weekStartTo ? lte(weeklyMetrics.weekStartIsoDate, args.weekStartTo) : undefined, args.metricKey ? eq(weeklyMetrics.metricKey, args.metricKey) : undefined);
|
|
90
|
+
const q = db
|
|
91
|
+
.select()
|
|
92
|
+
.from(weeklyMetrics)
|
|
93
|
+
.orderBy(desc(weeklyMetrics.weekStartIsoDate), asc(weeklyMetrics.metricKey))
|
|
94
|
+
.limit(applyLimit(args.limit));
|
|
95
|
+
const rows = where ? await q.where(where) : await q;
|
|
96
|
+
return {
|
|
97
|
+
rows: rows.map((r) => ({ ...r, valueType: inferValueType(r.metricKey) })),
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'list_daily_stress_burden',
|
|
103
|
+
description: 'List daily stress burden summaries. `peakScoreTz` is the offset-aware ISO string of `peakScoreUtc`.',
|
|
104
|
+
inputSchema: Type.Object({
|
|
105
|
+
isoDateFrom: Type.Optional(IsoDate),
|
|
106
|
+
isoDateTo: Type.Optional(IsoDate),
|
|
107
|
+
limit: Limit,
|
|
108
|
+
}, { additionalProperties: false }),
|
|
109
|
+
async handler(args) {
|
|
110
|
+
await tz.reload();
|
|
111
|
+
const where = and$(args.isoDateFrom ? gte(dailyStressBurden.isoDate, args.isoDateFrom) : undefined, args.isoDateTo ? lte(dailyStressBurden.isoDate, args.isoDateTo) : undefined);
|
|
112
|
+
const q = db
|
|
113
|
+
.select()
|
|
114
|
+
.from(dailyStressBurden)
|
|
115
|
+
.orderBy(desc(dailyStressBurden.isoDate))
|
|
116
|
+
.limit(applyLimit(args.limit));
|
|
117
|
+
const rows = where ? await q.where(where) : await q;
|
|
118
|
+
return {
|
|
119
|
+
rows: rows.map((r) => ({ ...r, peakScoreTz: tzIso(tz, r.peakScoreUtc) })),
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'list_heart_rate_minute',
|
|
125
|
+
description: 'List minute-aggregated heart rate rows. `minuteStartTz` is the offset-aware ISO string of `minuteStartUtc`. Use cursor pagination for large windows.',
|
|
126
|
+
inputSchema: Type.Object({
|
|
127
|
+
fromUtcMs: Type.Optional(UnixMs),
|
|
128
|
+
toUtcMs: Type.Optional(UnixMs),
|
|
129
|
+
deviceId: Type.Optional(Type.Integer()),
|
|
130
|
+
cursor: Type.Optional(UnixMs),
|
|
131
|
+
limit: Limit,
|
|
132
|
+
}, { additionalProperties: false }),
|
|
133
|
+
async handler(args) {
|
|
134
|
+
await tz.reload();
|
|
135
|
+
const lim = applyLimit(args.limit);
|
|
136
|
+
const where = and$(args.fromUtcMs !== undefined
|
|
137
|
+
? gte(heartRateMinute.minuteStartUtc, args.fromUtcMs)
|
|
138
|
+
: undefined, args.toUtcMs !== undefined
|
|
139
|
+
? lte(heartRateMinute.minuteStartUtc, args.toUtcMs)
|
|
140
|
+
: undefined, args.deviceId !== undefined ? eq(heartRateMinute.deviceId, args.deviceId) : undefined, args.cursor !== undefined ? gt(heartRateMinute.minuteStartUtc, args.cursor) : undefined);
|
|
141
|
+
const q = db
|
|
142
|
+
.select()
|
|
143
|
+
.from(heartRateMinute)
|
|
144
|
+
.orderBy(asc(heartRateMinute.minuteStartUtc))
|
|
145
|
+
.limit(lim);
|
|
146
|
+
const rows = where ? await q.where(where) : await q;
|
|
147
|
+
const decorated = rows.map((r) => ({ ...r, minuteStartTz: tzIso(tz, r.minuteStartUtc) }));
|
|
148
|
+
const last = rows[rows.length - 1];
|
|
149
|
+
const nextCursor = rows.length === lim && last ? last.minuteStartUtc : null;
|
|
150
|
+
return { rows: decorated, nextCursor };
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'list_sleep_sessions',
|
|
155
|
+
description: 'List sleep sessions with timezone-aware timestamps (sleepOnsetTz, wakeTimeTz, cycle startTz/endTz) and human-readable durations (wasoR, restorativeSleepR, sleepDeficitR as hh:mm:ss).',
|
|
156
|
+
inputSchema: Type.Object({
|
|
157
|
+
isoDateFrom: Type.Optional(IsoDate),
|
|
158
|
+
isoDateTo: Type.Optional(IsoDate),
|
|
159
|
+
limit: Limit,
|
|
160
|
+
}, { additionalProperties: false }),
|
|
161
|
+
async handler(args) {
|
|
162
|
+
await tz.reload();
|
|
163
|
+
const where = and$(args.isoDateFrom ? gte(sleepSessions.isoDate, args.isoDateFrom) : undefined, args.isoDateTo ? lte(sleepSessions.isoDate, args.isoDateTo) : undefined);
|
|
164
|
+
const q = db
|
|
165
|
+
.select()
|
|
166
|
+
.from(sleepSessions)
|
|
167
|
+
.orderBy(desc(sleepSessions.isoDate))
|
|
168
|
+
.limit(applyLimit(args.limit));
|
|
169
|
+
const rows = where ? await q.where(where) : await q;
|
|
170
|
+
return { rows: rows.map((r) => decorateSleepSession(r, tz)) };
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'list_hr_zone_history',
|
|
175
|
+
description: 'List HR zone boundary history. Each row records the bpm lower bounds for zones 1..5 effective from `effectiveFromIsoDate`.',
|
|
176
|
+
inputSchema: Type.Object({
|
|
177
|
+
effectiveFromDate: Type.Optional(IsoDate),
|
|
178
|
+
limit: Limit,
|
|
179
|
+
}, { additionalProperties: false }),
|
|
180
|
+
async handler(args) {
|
|
181
|
+
const where = args.effectiveFromDate
|
|
182
|
+
? gte(hrZoneHistory.effectiveFromIsoDate, args.effectiveFromDate)
|
|
183
|
+
: undefined;
|
|
184
|
+
const q = db
|
|
185
|
+
.select()
|
|
186
|
+
.from(hrZoneHistory)
|
|
187
|
+
.orderBy(desc(hrZoneHistory.effectiveFromIsoDate))
|
|
188
|
+
.limit(applyLimit(args.limit));
|
|
189
|
+
const rows = where ? await q.where(where) : await q;
|
|
190
|
+
return { rows };
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Decorate a sleep session row with timezone-aware timestamps and
|
|
197
|
+
* human-readable duration strings. The mobile schema deliberately stores the
|
|
198
|
+
* domain shape as opaque JSON, so we narrow defensively per field.
|
|
199
|
+
*/
|
|
200
|
+
function decorateSleepSession(row, tz) {
|
|
201
|
+
const payload = row.payload ?? {};
|
|
202
|
+
const decoratedPayload = { ...payload };
|
|
203
|
+
const sleepOnsetS = numberOrNull(payload.sleepOnsetS);
|
|
204
|
+
const wakeTimeS = numberOrNull(payload.wakeTimeS);
|
|
205
|
+
const wasoS = numberOrNull(payload.wasoS);
|
|
206
|
+
const restorativeSleepS = numberOrNull(payload.restorativeSleepS);
|
|
207
|
+
const sleepDeficitS = numberOrNull(payload.sleepDeficitS);
|
|
208
|
+
if (sleepOnsetS !== null)
|
|
209
|
+
decoratedPayload.sleepOnsetTz = tzIso(tz, sleepOnsetS * 1000);
|
|
210
|
+
if (wakeTimeS !== null)
|
|
211
|
+
decoratedPayload.wakeTimeTz = tzIso(tz, wakeTimeS * 1000);
|
|
212
|
+
if (wasoS !== null)
|
|
213
|
+
decoratedPayload.wasoR = formatHmsFromSeconds(wasoS);
|
|
214
|
+
if (restorativeSleepS !== null)
|
|
215
|
+
decoratedPayload.restorativeSleepR = formatHmsFromSeconds(restorativeSleepS);
|
|
216
|
+
if (sleepDeficitS !== null)
|
|
217
|
+
decoratedPayload.sleepDeficitR = formatHmsFromSeconds(sleepDeficitS);
|
|
218
|
+
const cycles = Array.isArray(payload.cycles) ? payload.cycles : [];
|
|
219
|
+
decoratedPayload.cycles = cycles.map((c) => {
|
|
220
|
+
if (!c || typeof c !== 'object')
|
|
221
|
+
return c;
|
|
222
|
+
const cycle = c;
|
|
223
|
+
const startS = numberOrNull(cycle.startS);
|
|
224
|
+
const endS = numberOrNull(cycle.endS);
|
|
225
|
+
return {
|
|
226
|
+
...cycle,
|
|
227
|
+
...(startS !== null ? { startTz: tzIso(tz, startS * 1000) } : {}),
|
|
228
|
+
...(endS !== null ? { endTz: tzIso(tz, endS * 1000) } : {}),
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
return { ...row, payload: decoratedPayload };
|
|
232
|
+
}
|
|
233
|
+
function numberOrNull(v) {
|
|
234
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a UTC instant as an ISO 8601 string with a timezone offset attached.
|
|
3
|
+
*
|
|
4
|
+
* formatTzIso(1716115262000, 120) === '2024-05-19T12:01:02+02:00'
|
|
5
|
+
*
|
|
6
|
+
* Seconds precision only — sub-second is intentionally dropped because none of
|
|
7
|
+
* the source timestamps carry it meaningfully.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatTzIso(utcMs: number, offsetMinutes: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Format a signed duration in seconds as `hh:mm:ss` (or `-hh:mm:ss`). Supports
|
|
12
|
+
* durations larger than 24h — hours are not modulo'd.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatHmsFromSeconds(totalSeconds: number): string;
|