diff --git a/services/mcp-server/src/lib/chronomind-client.ts b/services/mcp-server/src/lib/chronomind-client.ts index 94efa94e..2dd7ac17 100644 --- a/services/mcp-server/src/lib/chronomind-client.ts +++ b/services/mcp-server/src/lib/chronomind-client.ts @@ -52,6 +52,27 @@ export interface TimerDoc { syncVersion: number; } +export interface TimerCreateInput { + id: string; + label: string; + type: 'alarm' | 'countdown' | 'pomodoro' | 'event'; + state: 'idle' | 'active' | 'paused'; + urgency: 'critical' | 'important' | 'standard' | 'gentle' | 'passive'; + duration?: number; + targetTime?: string; + category?: string; + cascade?: unknown; + syncVersion: number; + deviceId?: string; +} + +export function chronomindTimerCreate( + input: TimerCreateInput, + opts: ChronoMindClientOptions +): Promise { + return chronomindFetch('/timers', { method: 'POST', body: JSON.stringify(input) }, opts); +} + export function chronomindTimersList( params: { limit?: number; offset?: number; type?: string; state?: string }, opts: ChronoMindClientOptions @@ -108,6 +129,19 @@ export function chronomindRoutinesList( return chronomindFetch(`/routines${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } +// ── Households ──────────────────────────────────────────────────────────── + +export function chronomindHouseholdsList( + params: { limit?: number; offset?: number }, + opts: ChronoMindClientOptions +): Promise<{ items: unknown[]; 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 chronomindFetch(`/households${q ? `?${q}` : ''}`, { method: 'GET' }, opts); +} + export function chronomindTimerDelete( timerId: string, opts: ChronoMindClientOptions diff --git a/services/mcp-server/src/lib/jarvis-client.ts b/services/mcp-server/src/lib/jarvis-client.ts index 2f5e9cee..51fbb84a 100644 --- a/services/mcp-server/src/lib/jarvis-client.ts +++ b/services/mcp-server/src/lib/jarvis-client.ts @@ -61,6 +61,27 @@ export interface JarvisAgentDoc { updatedAt: string; } +export interface AgentCreateInput { + name: string; + role: string; + systemPrompt: string; + voiceId?: string; + coachingFramework?: string; + accentColor?: string; + welcomeMessage?: string; + sessionLength?: number; + difficultyLevel?: 'beginner' | 'intermediate' | 'advanced'; + language?: string; + privacyLevel?: 'standard' | 'strict'; +} + +export function jarvisAgentCreate( + input: AgentCreateInput, + opts: JarvisClientOptions +): Promise { + return jarvisFetch('/jarvis/agents', { method: 'POST', body: JSON.stringify(input) }, opts); +} + export function jarvisAgentsList( params: { limit?: number; offset?: number }, opts: JarvisClientOptions @@ -160,6 +181,50 @@ export function jarvisMemoryList( ); } +// ── Marketplace admin ───────────────────────────────────────────────────── + +export function jarvisMarketplaceListPending( + opts: JarvisClientOptions +): Promise<{ listings: unknown[]; total: number }> { + return jarvisFetch('/marketplace/admin/pending', { method: 'GET' }, opts); +} + +export function jarvisMarketplaceCertify( + listingId: string, + decision: { decision: 'approved' | 'rejected'; notes?: string }, + opts: JarvisClientOptions +): Promise { + return jarvisFetch( + `/marketplace/admin/${listingId}/approve`, + { method: 'POST', body: JSON.stringify(decision) }, + opts + ); +} + +export function jarvisMarketplaceSuspend( + listingId: string, + reason: string, + opts: JarvisClientOptions +): Promise { + return jarvisFetch( + `/marketplace/admin/${listingId}/suspend`, + { method: 'POST', body: JSON.stringify({ reason }) }, + opts + ); +} + +export function jarvisMarketplaceFeature( + listingId: string, + featured: boolean, + opts: JarvisClientOptions +): Promise { + return jarvisFetch( + `/marketplace/admin/${listingId}/feature`, + { method: 'POST', body: JSON.stringify({ featured }) }, + opts + ); +} + export function jarvisMemoryPrune( agentId: string, opts: JarvisClientOptions diff --git a/services/mcp-server/src/lib/lysnrai-client.ts b/services/mcp-server/src/lib/lysnrai-client.ts index 2677c06c..69773b66 100644 --- a/services/mcp-server/src/lib/lysnrai-client.ts +++ b/services/mcp-server/src/lib/lysnrai-client.ts @@ -66,6 +66,13 @@ export function lysnraiTranscriptsList( return lysnraiFetch(`/transcripts${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } +export function lysnraiTranscriptGet( + id: string, + opts: LysnraiClientOptions +): Promise { + return lysnraiFetch(`/transcripts/${id}`, { method: 'GET' }, opts); +} + export function lysnraiTranscriptRunExtraction( transcriptId: string, opts: LysnraiClientOptions @@ -126,6 +133,10 @@ export interface OrgDoc { createdAt: string; } +export function lysnraiOrgGet(orgId: string, opts: LysnraiClientOptions): Promise { + return lysnraiFetch(`/orgs/${orgId}`, { method: 'GET' }, opts); +} + export function lysnraiOrgsList( params: { limit?: number; offset?: number }, opts: LysnraiClientOptions diff --git a/services/mcp-server/src/lib/mindlyst-client.ts b/services/mcp-server/src/lib/mindlyst-client.ts index 66bf7dd9..23392764 100644 --- a/services/mcp-server/src/lib/mindlyst-client.ts +++ b/services/mcp-server/src/lib/mindlyst-client.ts @@ -88,6 +88,13 @@ export function mindlystMemoryList( return mindlystFetch(`/memory-items${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } +export function mindlystMemoryGetItem( + itemId: string, + opts: MindlystClientOptions +): Promise { + return mindlystFetch(`/memory-items/${itemId}`, { method: 'GET' }, opts); +} + export function mindlystMemoryRetriage( itemId: string, opts: MindlystClientOptions diff --git a/services/mcp-server/src/lib/nomgap-client.ts b/services/mcp-server/src/lib/nomgap-client.ts index 407f07bd..c70f7df3 100644 --- a/services/mcp-server/src/lib/nomgap-client.ts +++ b/services/mcp-server/src/lib/nomgap-client.ts @@ -85,6 +85,22 @@ export function nomgapFastingGetWeeklyStats( return nomgapFetch('/fasting/stats/weekly', { method: 'GET' }, opts); } +// ── Protocols ───────────────────────────────────────────────────────────── + +export function nomgapProtocolsList( + opts: NomGapClientOptions +): Promise<{ protocols: unknown[]; total: number }> { + return nomgapFetch('/fasting/protocols', { method: 'GET' }, opts); +} + +// ── Body stages (public — no auth required) ──────────────────────────────── + +export function nomgapBodyStagesList( + opts: Pick +): Promise<{ stages: unknown[]; total: number }> { + return nomgapFetch('/fasting/stages', { method: 'GET' }, opts); +} + // ── Push triggers ────────────────────────────────────────────────────────── export type PushTriggerType = diff --git a/services/mcp-server/src/lib/peakpulse-client.ts b/services/mcp-server/src/lib/peakpulse-client.ts index 94a8cdac..8d82e1d1 100644 --- a/services/mcp-server/src/lib/peakpulse-client.ts +++ b/services/mcp-server/src/lib/peakpulse-client.ts @@ -105,6 +105,13 @@ export function peakpulseSessionsList( return peakpulseFetch(`/peak/sessions${q ? `?${q}` : ''}`, { method: 'GET' }, opts); } +export function peakpulseSessionGet( + sessionId: string, + opts: PeakPulseClientOptions +): Promise { + return peakpulseFetch(`/peak/sessions/${sessionId}`, { method: 'GET' }, opts); +} + export function peakpulseSessionExport( sessionId: string, opts: PeakPulseClientOptions diff --git a/services/mcp-server/src/modules/chronomind/chronomind-tools.ts b/services/mcp-server/src/modules/chronomind/chronomind-tools.ts index a453b38f..262ee8e6 100644 --- a/services/mcp-server/src/modules/chronomind/chronomind-tools.ts +++ b/services/mcp-server/src/modules/chronomind/chronomind-tools.ts @@ -9,15 +9,43 @@ import { z } from 'zod'; import { registerTool } from '../tools/registry.js'; import { config } from '../../lib/config.js'; import { + chronomindTimerCreate, chronomindTimersList, chronomindTimerDelete, chronomindRoutinesList, chronomindSyncStatus, + chronomindHouseholdsList, } from '../../lib/chronomind-client.js'; import type { McpToolRequest } from '../tools/types.js'; const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7); +// ── chronomind.timers.create ─────────────────────────────────────────────── + +registerTool({ + name: 'chronomind.timers.create', + description: + 'Create a new cloud-synced timer. The id and syncVersion must be provided by the caller (client-generated UUID and current version). Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + id: z.string().min(1).describe('Client-generated timer UUID'), + label: z.string().min(1).describe('Timer display name'), + type: z.enum(['alarm', 'countdown', 'pomodoro', 'event']), + urgency: z.enum(['critical', 'important', 'standard', 'gentle', 'passive']).default('standard'), + duration: z.coerce.number().optional().describe('Duration in seconds (countdown/pomodoro)'), + targetTime: z.string().optional().describe('ISO 8601 target time (alarm/event)'), + category: z.string().optional().describe('Timer category label'), + syncVersion: z.coerce.number().default(1), + deviceId: z.string().optional().describe('Origin device ID for conflict detection'), + }), + async execute(args, req) { + return chronomindTimerCreate( + { ...args, state: 'idle' }, + { token: tokenOf(req), requestId: req.id } + ); + }, +}); + // ── chronomind.timers.list ──────────────────────────────────────────────── registerTool({ @@ -88,3 +116,19 @@ registerTool({ return chronomindSyncStatus({ token: tokenOf(req), requestId: req.id }); }, }); + +// ── chronomind.households.list ──────────────────────────────────────────── + +registerTool({ + name: 'chronomind.households.list', + description: + 'List ChronoMind Family-tier households the authenticated user belongs to, including member list and invite codes. 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 chronomindHouseholdsList(args, { token: tokenOf(req), requestId: req.id }); + }, +}); diff --git a/services/mcp-server/src/modules/jarvis/jarvis-tools.ts b/services/mcp-server/src/modules/jarvis/jarvis-tools.ts index b48d8896..7d1d59bd 100644 --- a/services/mcp-server/src/modules/jarvis/jarvis-tools.ts +++ b/services/mcp-server/src/modules/jarvis/jarvis-tools.ts @@ -9,6 +9,7 @@ import { z } from 'zod'; import { registerTool } from '../tools/registry.js'; import { config } from '../../lib/config.js'; import { + jarvisAgentCreate, jarvisAgentsList, jarvisAgentDuplicate, jarvisSessionsList, @@ -16,11 +17,40 @@ import { jarvisMemoryList, jarvisMemoryPrune, jarvisMemoryGetContext, + jarvisMarketplaceListPending, + jarvisMarketplaceCertify, + jarvisMarketplaceSuspend, + jarvisMarketplaceFeature, } from '../../lib/jarvis-client.js'; import type { McpToolRequest } from '../tools/types.js'; const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7); +// ── jarvis.agents.create ───────────────────────────────────────────────── + +registerTool({ + name: 'jarvis.agents.create', + description: + 'Create a new coaching agent with a system prompt, role, and coaching framework. Returns the created agent including its ID. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + name: z.string().min(1).describe('Agent display name'), + role: z.string().min(1).describe('Agent role (e.g. Career Coach, Language Tutor)'), + systemPrompt: z.string().min(1).describe('System prompt for the agent'), + coachingFramework: z.string().optional().describe('Coaching methodology (e.g. GROW, CBT)'), + voiceId: z.string().optional().describe('TTS voice identifier'), + difficultyLevel: z + .enum(['beginner', 'intermediate', 'advanced']) + .optional() + .default('intermediate'), + language: z.string().optional().default('en'), + privacyLevel: z.enum(['standard', 'strict']).optional().default('standard'), + }), + async execute(args, req) { + return jarvisAgentCreate(args, { token: tokenOf(req), requestId: req.id }); + }, +}); + // ── jarvis.agents.list ──────────────────────────────────────────────────── registerTool({ @@ -148,3 +178,75 @@ registerTool({ }); }, }); + +// ── jarvis.marketplace.listPending ─────────────────────────────────────── + +registerTool({ + name: 'jarvis.marketplace.listPending', + description: + 'List marketplace listings awaiting certification (status = pending_review). For admin use in the certification pipeline. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({}), + async execute(_args, req) { + return jarvisMarketplaceListPending({ token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── jarvis.marketplace.certify ─────────────────────────────────────────── + +registerTool({ + name: 'jarvis.marketplace.certify', + description: + 'Approve or reject a marketplace listing. Sets isVerified flag and notifies the author. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + listingId: z.string().min(1).describe('Listing ID'), + decision: z.enum(['approved', 'rejected']).describe('Certification decision'), + notes: z.string().optional().describe('Optional review notes for the author'), + }), + async execute(args, req) { + const { listingId, ...decision } = args; + return jarvisMarketplaceCertify(listingId, decision, { + token: tokenOf(req), + requestId: req.id, + }); + }, +}); + +// ── jarvis.marketplace.suspend ─────────────────────────────────────────── + +registerTool({ + name: 'jarvis.marketplace.suspend', + description: + 'Suspend a published marketplace listing (e.g. policy violation). Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + listingId: z.string().min(1).describe('Listing ID to suspend'), + reason: z.string().min(1).describe('Suspension reason shown to the author'), + }), + async execute(args, req) { + return jarvisMarketplaceSuspend(args.listingId, args.reason, { + token: tokenOf(req), + requestId: req.id, + }); + }, +}); + +// ── jarvis.marketplace.feature ─────────────────────────────────────────── + +registerTool({ + name: 'jarvis.marketplace.feature', + description: + 'Toggle featured status on a marketplace listing (shows on homepage hero carousel). Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + listingId: z.string().min(1).describe('Listing ID'), + featured: z.boolean().describe('true = feature, false = unfeature'), + }), + async execute(args, req) { + return jarvisMarketplaceFeature(args.listingId, args.featured, { + token: tokenOf(req), + requestId: req.id, + }); + }, +}); diff --git a/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts b/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts index ed1310c0..69f0c32b 100644 --- a/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts +++ b/services/mcp-server/src/modules/lysnrai/lysnrai-tools.ts @@ -11,16 +11,33 @@ import { registerTool } from '../tools/registry.js'; import { config } from '../../lib/config.js'; import { lysnraiTranscriptsList, + lysnraiTranscriptGet, lysnraiTranscriptRunExtraction, lysnraiSttGetBackendStatus, lysnraiSessionsList, lysnraiOrgsList, + lysnraiOrgGet, lysnraiApiTokensList, } from '../../lib/lysnrai-client.js'; import type { McpToolRequest } from '../tools/types.js'; const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7); +// ── lysnrai.transcripts.get ─────────────────────────────────────────────── + +registerTool({ + name: 'lysnrai.transcripts.get', + description: + 'Get a single transcript by ID, including extractions[] and extractionMetadata if extraction has run. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + transcriptId: z.string().min(1).describe('Transcript ID'), + }), + async execute(args, req) { + return lysnraiTranscriptGet(args.transcriptId, { token: tokenOf(req), requestId: req.id }); + }, +}); + // ── lysnrai.transcripts.list ────────────────────────────────────────────── registerTool({ @@ -101,6 +118,21 @@ registerTool({ }, }); +// ── lysnrai.orgs.get ───────────────────────────────────────────────────── + +registerTool({ + name: 'lysnrai.orgs.get', + description: + 'Get a single organisation by ID including member count, vocabulary, and shared instructions. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + orgId: z.string().min(1).describe('Organisation ID'), + }), + async execute(args, req) { + return lysnraiOrgGet(args.orgId, { token: tokenOf(req), requestId: req.id }); + }, +}); + // ── lysnrai.apiTokens.list ──────────────────────────────────────────────── registerTool({ diff --git a/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts b/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts index 9e26537a..f6149a94 100644 --- a/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts +++ b/services/mcp-server/src/modules/mindlyst/mindlyst-tools.ts @@ -11,6 +11,7 @@ import { registerTool } from '../tools/registry.js'; import { config } from '../../lib/config.js'; import { mindlystMemoryList, + mindlystMemoryGetItem, mindlystMemoryRetriage, mindlystMemoryPatch, mindlystMemoryReassign, @@ -25,6 +26,21 @@ import type { McpToolRequest } from '../tools/types.js'; const tokenOf = (req: McpToolRequest) => req.headers.authorization?.slice(7); +// ── mindlyst.memory.get ─────────────────────────────────────────────────── + +registerTool({ + name: 'mindlyst.memory.get', + description: + 'Get a single memory item by ID, including its full TriageResult (urgencyScore, emotionScore, confidenceScore, entities, suggestedActions). Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + itemId: z.string().min(1).describe('Memory item ID'), + }), + async execute(args, req) { + return mindlystMemoryGetItem(args.itemId, { token: tokenOf(req), requestId: req.id }); + }, +}); + // ── mindlyst.memory.list ────────────────────────────────────────────────── registerTool({ diff --git a/services/mcp-server/src/modules/nomgap/nomgap-tools.ts b/services/mcp-server/src/modules/nomgap/nomgap-tools.ts index 02515249..98f7bfea 100644 --- a/services/mcp-server/src/modules/nomgap/nomgap-tools.ts +++ b/services/mcp-server/src/modules/nomgap/nomgap-tools.ts @@ -12,6 +12,8 @@ import { nomgapFastingSessionsList, nomgapFastingGetStats, nomgapFastingGetWeeklyStats, + nomgapProtocolsList, + nomgapBodyStagesList, nomgapPushFire, nomgapPushGetStats, nomgapPushGetPending, @@ -130,6 +132,32 @@ registerTool({ }, }); +// ── nomgap.protocols.list ────────────────────────────────────────────────── + +registerTool({ + name: 'nomgap.protocols.list', + description: + 'List all fasting protocols (built-in + user custom). Includes fastHours, eatHours, difficulty, and type. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({}), + async execute(_args, req) { + return nomgapProtocolsList({ token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── nomgap.bodyStages.list ─────────────────────────────────────────────── + +registerTool({ + name: 'nomgap.bodyStages.list', + description: + 'List all 6 fasting body stages (fed, early_fast, fasted, ketosis, deep_autophagy, extended) with hour thresholds and visualization metadata. Public endpoint — no auth needed.', + requiredRole: 'admin', + inputSchema: z.object({}), + async execute(_args, req) { + return nomgapBodyStagesList({ 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 77ee34d1..e60ee586 100644 --- a/services/mcp-server/src/modules/peakpulse/peakpulse-tools.ts +++ b/services/mcp-server/src/modules/peakpulse/peakpulse-tools.ts @@ -10,6 +10,7 @@ import { registerTool } from '../tools/registry.js'; import { config } from '../../lib/config.js'; import { peakpulseSessionsList, + peakpulseSessionGet, peakpulseSessionExport, peakpulseGetStats, peakpulseRouteGet, @@ -70,7 +71,22 @@ registerTool({ }, }); -// ── peakpulse.routes.get ────────────────────────────────────────────────── +// ── peakpulse.sessions.get ────────────────────────────────────────────────── + +registerTool({ + name: 'peakpulse.sessions.get', + description: + 'Get a single adventure session by ID including activityType, GPS trackPoints summary, ski/hike metrics, weather snapshot, and haptic events. Requires admin role.', + requiredRole: 'admin', + inputSchema: z.object({ + sessionId: z.string().min(1).describe('Session ID'), + }), + async execute(args, req) { + return peakpulseSessionGet(args.sessionId, { token: tokenOf(req), requestId: req.id }); + }, +}); + +// ── peakpulse.routes.get ──────────────────────────────────────────────────── registerTool({ name: 'peakpulse.routes.get',