feat(mcp-server): fill 12 DOMAIN_PRODUCTS.md MCP tool gaps + client fn additions
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
This commit is contained in:
parent
1da3394caf
commit
3f296a8e72
@ -117,6 +117,13 @@ export interface RoutineDoc {
|
||||
syncVersion: number;
|
||||
}
|
||||
|
||||
export function chronomindRoutineGet(
|
||||
routineId: string,
|
||||
opts: ChronoMindClientOptions
|
||||
): Promise<RoutineDoc> {
|
||||
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
|
||||
|
||||
@ -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<ApiTokenDoc & { rawToken: string }> {
|
||||
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<Record<string, unknown>> {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -61,6 +61,13 @@ export interface FastingSessionDoc {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function nomgapFastingCreateSession(
|
||||
input: { protocolId: string; variables?: Record<string, string> },
|
||||
opts: NomGapClientOptions
|
||||
): Promise<FastingSessionDoc> {
|
||||
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<Record<string, unknown>> {
|
||||
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;
|
||||
|
||||
@ -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<PeakRouteDoc> {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user