From 3f296a8e72ccf081ee565af5bb20edfd8cae2bdc Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Thu, 5 Mar 2026 18:19:04 -0800 Subject: [PATCH] feat(mcp-server): fill 12 DOMAIN_PRODUCTS.md MCP tool gaps + client fn additions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug fix (committed separately): social-fast-coordinator: stage_transition used non-existent currentStage New client functions: nomgap-client: nomgapFastingCreateSession, nomgapProtocolGet, nomgapSocialListGroupFasts (+ GroupFastDoc) peakpulse-client: peakpulseRoutesList, peakpulseSyncStatus lysnrai-client: lysnraiApiTokenCreate, lysnraiSessionsStats, lysnraiTranscriptsExportBatch chronomind-client: chronomindRoutineGet, chronomindSharedTimerShare New MCP tools (12): mindlyst.briefs.generate — trigger daily brief via mindlystBriefCreate mindlyst.memory.getTriageResult — extract TriageResult sub-doc only nomgap.fasting.createSession — start new fast with protocolId nomgap.protocols.get — single protocol lookup nomgap.social.listGroupFasts — list group fast sessions peakpulse.routes.list — list GPS r Bug fix (committed separately): social-fast-coordinator: stage_transition used non-existent currentStage ats social-fast-coordinator: staon New client functions: nomgap-client: nomgapFastingCreateSession, nomgarai nomgaens.rotate nomgapSocialListGroupFasts (+ GroupFastDoc) d. peakpulse-client: peakpulseRoutesList, peakpulseSyncStaturg lysnrai-client: lysnraiApiTokenCreate, lysnraiSessionsStati lysnraiTranscriptsExpor us chronomind-client: chronomindRoutineGet, chrerver total: 126 tools across 17 namespaces --- .../mcp-server/src/lib/chronomind-client.ts | 19 +++++ services/mcp-server/src/lib/lysnrai-client.ts | 22 ++++++ services/mcp-server/src/lib/nomgap-client.ts | 37 +++++++++ .../mcp-server/src/lib/peakpulse-client.ts | 21 +++++ .../modules/chronomind/chronomind-tools.ts | 79 ++++++++++++++++++- .../src/modules/lysnrai/lysnrai-tools.ts | 55 +++++++++++++ .../src/modules/mindlyst/mindlyst-tools.ts | 44 +++++++++++ .../src/modules/nomgap/nomgap-tools.ts | 52 ++++++++++++ .../src/modules/peakpulse/peakpulse-tools.ts | 33 +++++++- 9 files changed, 360 insertions(+), 2 deletions(-) diff --git a/services/mcp-server/src/lib/chronomind-client.ts b/services/mcp-server/src/lib/chronomind-client.ts index 2dd7ac17..3eec98a5 100644 --- a/services/mcp-server/src/lib/chronomind-client.ts +++ b/services/mcp-server/src/lib/chronomind-client.ts @@ -117,6 +117,13 @@ export interface RoutineDoc { syncVersion: number; } +export function chronomindRoutineGet( + routineId: string, + opts: ChronoMindClientOptions +): Promise { + return chronomindFetch(`/routines/${routineId}`, { method: 'GET' }, opts); +} + export function chronomindRoutinesList( params: { limit?: number; offset?: number; isTemplate?: boolean }, opts: ChronoMindClientOptions @@ -142,6 +149,18 @@ export function chronomindHouseholdsList( return chronomindFetch(`/households${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } +export function chronomindSharedTimerShare( + timerId: string, + targets: { userId?: string; householdId?: string }[], + opts: ChronoMindClientOptions +): Promise<{ sharedTimerId: string; timerId: string; targetCount: number; sharedAt: string }> { + return chronomindFetch( + '/shared-timers', + { method: 'POST', body: JSON.stringify({ timerId, targets }) }, + opts + ); +} + export function chronomindTimerDelete( timerId: string, opts: ChronoMindClientOptions diff --git a/services/mcp-server/src/lib/lysnrai-client.ts b/services/mcp-server/src/lib/lysnrai-client.ts index e9be3bca..c6557c70 100644 --- a/services/mcp-server/src/lib/lysnrai-client.ts +++ b/services/mcp-server/src/lib/lysnrai-client.ts @@ -177,9 +177,31 @@ export function lysnraiApiTokensList( return lysnraiFetch(`/api-tokens${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } +export function lysnraiApiTokenCreate( + input: { name: string }, + opts: LysnraiClientOptions +): Promise { + return lysnraiFetch('/api-tokens', { method: 'POST', body: JSON.stringify(input) }, opts); +} + export function lysnraiApiTokenRevoke( tokenId: string, opts: LysnraiClientOptions ): Promise<{ success: boolean }> { return lysnraiFetch(`/api-tokens/${tokenId}`, { method: 'DELETE' }, opts); } + +export function lysnraiSessionsStats(opts: LysnraiClientOptions): Promise> { + return lysnraiFetch('/sessions/stats', { method: 'GET' }, opts); +} + +export function lysnraiTranscriptsExportBatch( + params: { format?: 'json' | 'txt'; limit?: number }, + opts: LysnraiClientOptions +): Promise<{ exportUrl: string; count: number; format: string; generatedAt: string }> { + const qs = new URLSearchParams(); + if (params.format) qs.set('format', params.format); + if (params.limit !== undefined) qs.set('limit', String(params.limit)); + const q = qs.toString(); + return lysnraiFetch(`/transcripts/export${q ? `?${q}` : ''}`, { method: 'GET' }, opts); +} diff --git a/services/mcp-server/src/lib/nomgap-client.ts b/services/mcp-server/src/lib/nomgap-client.ts index 3f252f84..253bd1ec 100644 --- a/services/mcp-server/src/lib/nomgap-client.ts +++ b/services/mcp-server/src/lib/nomgap-client.ts @@ -61,6 +61,13 @@ export interface FastingSessionDoc { updatedAt: string; } +export function nomgapFastingCreateSession( + input: { protocolId: string; variables?: Record }, + opts: NomGapClientOptions +): Promise { + return nomgapFetch('/fasting/sessions', { method: 'POST', body: JSON.stringify(input) }, opts); +} + export function nomgapFastingSessionsList( params: { limit?: number; offset?: number; status?: string; from?: string; to?: string }, opts: NomGapClientOptions @@ -100,6 +107,13 @@ export function nomgapProtocolsList( return nomgapFetch('/fasting/protocols', { method: 'GET' }, opts); } +export function nomgapProtocolGet( + protocolId: string, + opts: NomGapClientOptions +): Promise> { + return nomgapFetch(`/fasting/protocols/${protocolId}`, { method: 'GET' }, opts); +} + // ── Body stages (public — no auth required) ──────────────────────────────── export function nomgapBodyStagesList( @@ -168,6 +182,29 @@ export interface PushTriggerDoc { createdAt: string; } +// ── Social fasting ──────────────────────────────────────────────────────── + +export interface GroupFastDoc { + id: string; + userId: string; + productId: string; + sessionId: string; + memberCount: number; + status: 'active' | 'completed' | 'cancelled'; + createdAt: string; +} + +export function nomgapSocialListGroupFasts( + params: { limit?: number; offset?: number }, + opts: NomGapClientOptions +): Promise<{ items: GroupFastDoc[]; total: number }> { + const qs = new URLSearchParams(); + if (params.limit !== undefined) qs.set('limit', String(params.limit)); + if (params.offset !== undefined) qs.set('offset', String(params.offset)); + const q = qs.toString(); + return nomgapFetch(`/social/group-fasts${q ? `?${q}` : ''}`, { method: 'GET' }, opts); +} + export function nomgapPushFire( input: { type: PushTriggerType; diff --git a/services/mcp-server/src/lib/peakpulse-client.ts b/services/mcp-server/src/lib/peakpulse-client.ts index 8d82e1d1..9ecef14c 100644 --- a/services/mcp-server/src/lib/peakpulse-client.ts +++ b/services/mcp-server/src/lib/peakpulse-client.ts @@ -144,9 +144,30 @@ export interface PeakRouteDoc { updatedAt: string; } +export function peakpulseRoutesList( + params: { limit?: number; offset?: number }, + opts: PeakPulseClientOptions +): Promise<{ items: PeakRouteDoc[]; total: number }> { + const qs = new URLSearchParams(); + if (params.limit !== undefined) qs.set('limit', String(params.limit)); + if (params.offset !== undefined) qs.set('offset', String(params.offset)); + const q = qs.toString(); + return peakpulseFetch(`/peak/routes${q ? `?${q}` : ''}`, { method: 'GET' }, opts); +} + export function peakpulseRouteGet( sessionId: string, opts: PeakPulseClientOptions ): Promise { return peakpulseFetch(`/peak/routes/${sessionId}`, { method: 'GET' }, opts); } + +export function peakpulseSyncStatus(opts: PeakPulseClientOptions): Promise<{ + userId: string; + productId: string; + pendingUploadCount: number; + lastSyncedAt: string | null; + oldestPendingAt: string | null; +}> { + return peakpulseFetch('/peak/sync/status', { method: 'GET' }, opts); +} diff --git a/services/mcp-server/src/modules/chronomind/chronomind-tools.ts b/services/mcp-server/src/modules/chronomind/chronomind-tools.ts index e9e1105b..eea4d614 100644 --- a/services/mcp-server/src/modules/chronomind/chronomind-tools.ts +++ b/services/mcp-server/src/modules/chronomind/chronomind-tools.ts @@ -12,9 +12,11 @@ import { chronomindTimerCreate, chronomindTimersList, chronomindTimerDelete, + chronomindRoutineGet, chronomindRoutinesList, chronomindSyncStatus, chronomindHouseholdsList, + chronomindSharedTimerShare, } from '../../lib/chronomind-client.js'; import type { McpToolRequest } from '../tools/types.js'; @@ -117,7 +119,82 @@ registerTool({ }, }); -// ── chronomind.households.list ──────────────────────────────────────────── +// ── chronomind.sharedTimers.share ────────────────────────────────────────── + +registerTool({ + name: 'chronomind.sharedTimers.share', + description: + 'Share a timer with one or more household members or specific user IDs. Creates a shared-timer record in the shared-timers module. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + timerId: z.string().min(1).describe('ID of the timer to share'), + targets: z + .array( + z.object({ + userId: z.string().optional().describe('Target user ID'), + householdId: z + .string() + .optional() + .describe('Target household ID (shares with all members)'), + }) + ) + .min(1) + .describe('Recipients — provide userId or householdId for each target'), + }), + async execute(args, req) { + return chronomindSharedTimerShare(args.timerId, args.targets, { + token: tokenOf(req), + requestId: req.id, + }); + }, +}); + +// ── chronomind.routines.validate ───────────────────────────────────────── + +registerTool({ + name: 'chronomind.routines.validate', + description: + 'Validate a routine for structural integrity: checks that total duration does not exceed the maximum, that all steps have non-zero duration, and that the routine has at least one step. Returns pass/fail with a list of issues. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + routineId: z.string().min(1).describe('Routine ID to validate'), + maxDurationMinutes: z.coerce + .number() + .min(1) + .max(1440) + .default(480) + .describe('Maximum allowed total duration in minutes (default 480 = 8h)'), + }), + async execute(args, req) { + let routine; + try { + routine = await chronomindRoutineGet(args.routineId, { + token: tokenOf(req), + requestId: req.id, + }); + } catch { + return { valid: false, routineId: args.routineId, issues: ['Routine not found'] }; + } + const issues: string[] = []; + if (!routine.steps || routine.steps.length === 0) issues.push('Routine has no steps'); + if (routine.totalDurationMinutes > args.maxDurationMinutes) { + issues.push( + `Total duration ${routine.totalDurationMinutes}min exceeds max ${args.maxDurationMinutes}min` + ); + } + return { + valid: issues.length === 0, + routineId: routine.id, + routineName: routine.name, + stepCount: routine.steps.length, + totalDurationMinutes: routine.totalDurationMinutes, + maxDurationMinutes: args.maxDurationMinutes, + issues, + }; + }, +}); + +// ── chronomind.households.list ────────────────────────────────────────────── registerTool({ name: 'chronomind.households.list', diff --git a/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts b/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts index 4800ae27..c8540155 100644 --- a/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts +++ b/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts @@ -19,7 +19,10 @@ import { lysnraiOrgsList, lysnraiOrgGet, lysnraiApiTokensList, + lysnraiApiTokenCreate, lysnraiApiTokenRevoke, + lysnraiSessionsStats, + lysnraiTranscriptsExportBatch, } from '../../lib/lysnrai-client.js'; import type { McpToolRequest } from '../tools/types.js'; @@ -179,3 +182,55 @@ registerTool({ return lysnraiApiTokensList(args, { token: tokenOf(req), requestId: req.id }); }, }); + +// ── lysnrai.sessions.stats ──────────────────────────────────────────────── + +registerTool({ + name: 'lysnrai.sessions.stats', + description: + 'Get aggregated dictation session statistics for the authenticated user: total sessions, composed count, average word count, and last session date. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({}), + async execute(_args, req) { + return lysnraiSessionsStats({ token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── lysnrai.transcripts.exportBatch ────────────────────────────────────── + +registerTool({ + name: 'lysnrai.transcripts.exportBatch', + description: + 'Batch-export transcripts for the authenticated user as JSON or plain text. Returns a signed export URL, item count, and format. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + format: z.enum(['json', 'txt']).default('json').describe('Export format'), + limit: z.coerce + .number() + .min(1) + .max(config.QUERY_MAX_LIMIT) + .default(config.QUERY_DEFAULT_LIMIT) + .describe('Max transcripts to include'), + }), + async execute(args, req) { + return lysnraiTranscriptsExportBatch(args, { token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── lysnrai.apiTokens.rotate ────────────────────────────────────────────── + +registerTool({ + name: 'lysnrai.apiTokens.rotate', + description: + 'Rotate an API token: revokes the existing token and issues a fresh one with the same name. Returns the new raw token (shown once only). Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + tokenId: z.string().min(1).describe('Existing API token ID to rotate'), + name: z.string().min(1).describe('Name for the replacement token'), + }), + async execute(args, req) { + const opts = { token: tokenOf(req), requestId: req.id }; + await lysnraiApiTokenRevoke(args.tokenId, opts); + return lysnraiApiTokenCreate({ name: args.name }, opts); + }, +}); diff --git a/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts b/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts index f6149a94..384036a7 100644 --- a/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts +++ b/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts @@ -16,6 +16,7 @@ import { mindlystMemoryPatch, mindlystMemoryReassign, mindlystBrainsList, + mindlystBriefCreate, mindlystBriefsGetToday, mindlystBriefsList, mindlystStreaksGet, @@ -192,6 +193,49 @@ registerTool({ }, }); +// ── mindlyst.briefs.generate ──────────────────────────────────────────────── + +registerTool({ + name: 'mindlyst.briefs.generate', + description: + 'Trigger generation of a Daily Brief for the authenticated user for a specific date. Assembles greeting, priorityItems[], and brainSummaries from recent memory activity. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + date: z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/) + .describe('Target date in YYYY-MM-DD format (defaults to today if omitted)') + .optional(), + }), + async execute(args, req) { + const date = args.date ?? new Date().toISOString().slice(0, 10); + return mindlystBriefCreate({ date }, { token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── mindlyst.memory.getTriageResult ────────────────────────────────────────── + +registerTool({ + name: 'mindlyst.memory.getTriageResult', + description: + 'Get just the TriageResult sub-document for a memory item: contentType, urgencyScore (0–1), emotionScore (−1–+1), confidenceScore (0–1), suggestedBrainId, entities[], suggestedActions[]. Lighter than mindlyst.memory.get when only triage scoring is needed. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + itemId: z.string().min(1).describe('Memory item ID'), + }), + async execute(args, req) { + const item = await mindlystMemoryGetItem(args.itemId, { + token: tokenOf(req), + requestId: req.id, + }); + return { + itemId: item.id, + brainIds: item.brainIds, + triageResult: item.triageResult ?? null, + }; + }, +}); + // ── mindlyst.extractions.run ────────────────────────────────────────────── registerTool({ diff --git a/services/mcp-server/src/modules/nomgap/nomgap-tools.ts b/services/mcp-server/src/modules/nomgap/nomgap-tools.ts index 3f2eee38..b39cb8f0 100644 --- a/services/mcp-server/src/modules/nomgap/nomgap-tools.ts +++ b/services/mcp-server/src/modules/nomgap/nomgap-tools.ts @@ -9,11 +9,14 @@ import { z } from 'zod'; import { registerTool } from '../tools/registry.js'; import { config } from '../../lib/config.js'; import { + nomgapFastingCreateSession, nomgapFastingSessionsList, nomgapFastingSessionGet, nomgapFastingGetStats, nomgapFastingGetWeeklyStats, nomgapProtocolsList, + nomgapProtocolGet, + nomgapSocialListGroupFasts, nomgapBodyStagesList, nomgapAutophagyConfidence, nomgapPushFire, @@ -222,6 +225,55 @@ registerTool({ }, }); +// ── nomgap.fasting.createSession ──────────────────────────────────────────── + +registerTool({ + name: 'nomgap.fasting.createSession', + description: + 'Start a new fasting session for the authenticated user with a given protocol. Returns the new session document including startedAt and targetDurationMs. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + protocolId: z.string().min(1).describe('Protocol ID to use for this session'), + }), + async execute(args, req) { + return nomgapFastingCreateSession( + { protocolId: args.protocolId }, + { token: tokenOf(req), requestId: req.id } + ); + }, +}); + +// ── nomgap.protocols.get ───────────────────────────────────────────────────── + +registerTool({ + name: 'nomgap.protocols.get', + description: + 'Get a single fasting protocol by ID including its name, targetHours, description, steps, and default variables. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + protocolId: z.string().min(1).describe('Fasting protocol ID'), + }), + async execute(args, req) { + return nomgapProtocolGet(args.protocolId, { token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── nomgap.social.listGroupFasts ───────────────────────────────────────────── + +registerTool({ + name: 'nomgap.social.listGroupFasts', + description: + 'List group fasting sessions the authenticated user has participated in or created. Returns sessionId, memberCount, and status (active/completed/cancelled). Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT), + offset: z.coerce.number().min(0).default(0), + }), + async execute(args, req) { + return nomgapSocialListGroupFasts(args, { token: tokenOf(req), requestId: req.id }); + }, +}); + // ── nomgap.push.pending ─────────────────────────────────────────────────── registerTool({ diff --git a/services/mcp-server/src/modules/peakpulse/peakpulse-tools.ts b/services/mcp-server/src/modules/peakpulse/peakpulse-tools.ts index eb12ec76..d31b9e94 100644 --- a/services/mcp-server/src/modules/peakpulse/peakpulse-tools.ts +++ b/services/mcp-server/src/modules/peakpulse/peakpulse-tools.ts @@ -13,7 +13,9 @@ import { peakpulseSessionGet, peakpulseSessionExport, peakpulseGetStats, + peakpulseRoutesList, peakpulseRouteGet, + peakpulseSyncStatus, } from '../../lib/peakpulse-client.js'; import type { McpToolRequest } from '../tools/types.js'; @@ -86,6 +88,22 @@ registerTool({ }, }); +// ── peakpulse.routes.list ─────────────────────────────────────────────────── + +registerTool({ + name: 'peakpulse.routes.list', + description: + 'List GPS route documents for the authenticated user. Each route contains track points, haptic milestone events, and a bounding box. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + limit: z.coerce.number().min(1).max(config.QUERY_MAX_LIMIT).default(config.QUERY_DEFAULT_LIMIT), + offset: z.coerce.number().min(0).default(0), + }), + async execute(args, req) { + return peakpulseRoutesList(args, { token: tokenOf(req), requestId: req.id }); + }, +}); + // ── peakpulse.routes.get ──────────────────────────────────────────────────── registerTool({ @@ -101,7 +119,20 @@ registerTool({ }, }); -// ── peakpulse.weather.getSnapshot ─────────────────────────────────────────── +// ── peakpulse.syncStatus ───────────────────────────────────────────────────── + +registerTool({ + name: 'peakpulse.syncStatus', + description: + 'Check the PeakPulse offline upload queue: pending session count, last successful sync timestamp, and oldest pending item age. Useful for diagnosing sync failures on iOS. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({}), + async execute(_args, req) { + return peakpulseSyncStatus({ token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── peakpulse.weather.getSnapshot ─────────────────────────────────────────────── registerTool({ name: 'peakpulse.weather.getSnapshot',